Category Archives: WSS 3.0/MOSS 2007

Windows SharePoint Services 3.0/Microsoft Office SharePoint Server 2007

Publicar formularios InfoPath con código en SharePoint

3
Filed under WSS 3.0/MOSS 2007

"Microsoft Office Forms Server 2007" permite que usuarios puedan trabajar con formularios de "Microsoft Office InfoPath 2007" sin la necesidad de tener instalado InfoPath.
Para que un formulario pueda ser visualizado en un navegador Web, tenemos que cambiar la configuración de compatibilidad de la plantilla. Además, si queremos escribir código administrado (C# o Visual Basic .NET), el proceso de despliegue y activación del formulario es distinto. A las plantillas de formulario que utilizan código administrado se las conoce como "administrator-approved form templates". Debido a que código administrado puede ser un problema de seguridad, los formularios que contengan código deben ser aprobados por un administrador.

Diseñar el formulario
1 – Abrimos "Microsoft Office InfoPath 2007" y en "Introducción" seleccionamos "Diseñar una plantilla de formulario…". Elegimos "En blanco" y hacemos clic en "Aceptar".
2 – En el "Panel de tareas", hacemos clic en "Verificador de diseño" y luego "Cambiar configuración de compatibilidad…". En "Opciones de formulario" seleccionamos la categoría "Compatibilidad y marcamos la opción "Diseñe una plantilla de formulario que se pueda abrir en un explorador o en InfoPath". Finalmente hacemos clic en "Aceptar".

[Haga clic en la imagen para ampliar]

[Haga clic en la imagen para ampliar]


3 – Volvemos a "Tareas de diseño" y hacemos clic en "Controles". Arrastramos hacia la superficie de diseño un control "Cuadro de texto" y un control "Botón". Cuando el usuario haga clic en "Botón", cambiaremos a mayúsculas el texto que haya en el cuadro de texto.

[Haga clic en la imagen para ampliar]


4 – Para que nuestro formulario pueda utilizar código administrado, tiene que tener plena confianza. Vamos al menú "Herramientas" y hacemos clic en "Opciones de formulario…". Seleccionamos la categoría "Seguridad y confianza" y le sacamos la marca a "Determinar automáticamente el nivel de seguridad (recomendado)" para así poder elegir "Plena confianza". Elegimos la opción "Plena confianza (el formulario puede obtener acceso a los archivos y a la configuración del equipo)". Hacemos clic en "Aceptar".

[Haga clic en la imagen para ampliar]


Agregar código
1 – Podemos elegir escribir el código del formulario en C# o Visual Basic .NET. Para configurar esto, vamos al menú "Herramientas" y hacemos clic en "Opciones de formulario…". Bajo "Programación" elegimos "C#" en "Lenguaje del código de la plantilla de formulario" y hacemos clic en "Aceptar".

[Haga clic en la imagen para ampliar]


2 – Una vez que estamos en el diseñador, hacemos clic derecho sobre "Botón" y seleccionamos "Propiedades de Botón". Hacemos clic sobre "Editar código del formulario…", veremos un mensaje diciendo que para poder agregar código tenemos que guardar la plantilla primero. Le damos a "Aceptar" y elegimos dónde guardar la plantilla. Después, se abrirá "Microsoft Visual Studio Tools para aplicaciones".

[Haga clic en la imagen para ampliar]


3 – En el manejador "CTRL2_5_Clicked" para el evento "Clicked" de "CTRL2_5" escribimos el siguiente código:

        public void CTRL2_5_Clicked(object sender, ClickedEventArgs e)
        {
            XPathNavigator lcampo1 =
                e.Source.SelectSingleNode("/my:misCampos/my:campo1", this.NamespaceManager);
            lcampo1.SetValue(lcampo1.InnerXml.ToUpper());
        }

"/my:misCampos/my:campo1" es la expresión XPath al cuadro de texto que agregamos anteriormente. Para obtener la expresión, en "Tareas de diseño" seleccionamos "Origen de datos" y buscamos el campo que está enlazado al control cuadro de texto. Hacemos clic derecho sobre el campo y seleccionamos "Copiar XPath".

[Haga clic en la imagen para ampliar]


Activar la característica "Características de colección de sitios de Office SharePoint Server Enterprise"
1 – Vamos al sitio de nivel superior en la colección de sitios para el sitio que queremos mostrar el formulario de InfoPath.
2 – En el menú "Acciones del sitio" seleccionamos "Configuración del sitio" y bajo la sección "Administración de la colección de sitios" elegimos "Características de la colección de sitios". Buscamos "Características de colección de sitios de Office SharePoint Server Enterprise" y hacemos clic en el botón "Activar".

Publicar el formulario
1 – Vamos al menú "Archivo" y hacemos clic en "Publicar…", nos saldrá un mensaje para guardar el formulario debido a que ha sido modificado desde la última vez que fue guardado. Hacemos clic en "Aceptar" y en el asistente para la publicación, elegimos "En un servidor de SharePoint con o sin InfoPath Forms Services".

[Haga clic en la imagen para ampliar]


2 – Hacemos clic en "Siguiente". Especificamos la ubicación del sitio de SharePoint o InfoPath Forms Services. En mi caso "http://equipo:2667/".

[Haga clic en la imagen para ampliar]


3 – Hacemos clic en "Siguiente". Vamos a ver que la única opción que tenemos es "Plantilla de formulario aprobada por el administrador (avanzado)".

[Haga clic en la imagen para ampliar]


4 – Hacemos clic en "Siguiente". En "Especifique una ubicación y un nombre de archivo para la plantilla de formulario", hacemos clic en "Examinar…" y elegimos en dónde guardar la plantilla de formulario publicada para que un administrador luego la suba y la active.

[Haga clic en la imagen para ampliar]


5 – Hacemos clic en "Siguiente" dos veces hasta llegar al paso en el que tenemos que hacer clic en "Publicar".

[Haga clic en la imagen para ampliar]


Instalar la plantilla de formulario
1 – Abrimos "Símbolo del sistema" y nos vamos a la ubicación en la que se encuentra la herramienta "STSADM.EXE" (en mi caso "C:\Archivos de programa\Archivos comunes\Microsoft Shared\web server extensions\12\BIN").
2 – Ejecutamos los siguientes comandos (cambiando por supuesto los valores específicos a nuestro entorno):

stsadm.exe -o deactivateformtemplate -url "http://equipo:2667/" -filename "C:\Plantillas\Plantilla1_Publicada.xsn"
stsadm.exe -o removeformtemplate -filename "C:\Plantillas\Plantilla1_Publicada.xsn"
stsadm.exe -o execadmsvcjobs
stsadm.exe -o uploadformtemplate -filename "C:\Plantillas\Plantilla1_Publicada.xsn"
stsadm.exe -o execadmsvcjobs
stsadm.exe -o activateformtemplate -url "http://equipo:2667/" -filename "C:\Plantillas\Plantilla1_Publicada.xsn"

[Haga clic en la imagen para ampliar]


deactivateformtemplate, removeformtemplate – Nos aseguramos de desactivar y remover el formulario por si estamos actualizando y no instalando por primera vez.
execadmsvcjobs – Ejecuta todos los trabajos administrativos del temporizador inmediatamente en lugar de esperar a que se ejecute el trabajo del temporizador.
uploadformtemplate, activateformtemplate – Subimos la plantilla de formulario y la activamos.

Mostrar la plantilla de formulario en un sitio
1 – Vamos al sitio para el que queremos mostrar el formulario (tiene que ser un sitio que pertenezca a la colección de sitios para la que activamos la plantilla).
2 – En el menú "Acciones del sitio" seleccionamos "Configuración del sitio" y bajo la sección "Administración de sitios" elegimos "Bibliotecas y listas del sitio".
3 – En "Bibliotecas y listas del sitio" elegimos "Crear nuevo contenido" y bajo la sección "Bibliotecas" seleccionamos "Biblioteca de formularios".
4 – Establecemos los parámetros de creación y hacemos clic en "Crear".

[Haga clic en la imagen para ampliar]


5 – Una vez que hicimos clic en "Crear" vamos a ser redirigidos a la biblioteca. En "Configuración" elegimos "Configuración de Biblioteca de formularios" y bajo "Configuración general" seleccionamos "Configuración avanzada". En "Tipos de contenido" elegimos "Sí" para "¿Desea permitir la administración de tipos de contenido?" y en "Documentos habilitados por el explorador" elegimos "Mostrar como página Web". Hacemos clic en "Aceptar".

[Haga clic en la imagen para ampliar]


6 – Volvemos a "Personalizar Formularios InfoPath". Bajo "Tipos de contenido" hacemos clic en "Agregar a partir de tipos de contenido de sitio" y buscamos en el grupo "Microsoft Office InfoPath" el tipo de contenido "Plantilla1_Publicada". Lo agregamos haciendo clic en "Agregar" y hacemos clic en "Aceptar".

[Haga clic en la imagen para ampliar]


7 – Bajo "Tipos de contenido" hacemos clic en "Formulario" y elegimos "Eliminar este tipo de contenido".

Probar
1 – Vamos a la biblioteca "Formularios InfoPath" y hacemos clic en "Nuevo", "Plantilla1_Publicada".

[Haga clic en la imagen para ampliar]

ASP.NET Web Parts: crear un EditorPart y desplegar el Web Part en SharePoint

7
Filed under WSS 3.0/MOSS 2007

Todo desarrollo Web Part que tenga como objetivo WSS 3.0/MOSS 2007 debería utilizar como clase base "System.Web.UI.WebControls.WebParts.WebPart". "Microsoft.SharePoint.WebPartPages.WebPart" se mantiene principalmente por un tema de compatibilidad con versiones anteriores de SharePoint. En este artículo vamos a crear un simple Web Part que tendrá una propiedad que el usuario establecerá para recibir respuesta. El despliegue del ensamblado y su configuración lo haremos manualmente.

Abrimos Visual Studio y creamos un nuevo proyecto de tipo Biblioteca de clases al que llamaremos "WebPartSaludo". Antes de empezar a codificar:
1 – Agregamos una referencia al ensamblado "System.Web". Esta referencia es necesaria para hacer que nuestro Web Part herede de "System.Web.UI.WebControls.WebParts.WebPart".
2 – Renombramos "Class1.cs" por "SaludoWebPart.cs" que automáticamente debería cambiar el nombre de la clase. Si no es así cambiamos el nombre de la clase.
3 – Hacemos heredar la clase "SaludoWebPart" de "System.Web.UI.WebControls.WebParts.WebPart".

Nuestro Web Part pedirá al usuario un nombre y mostrará por ejemplo "Hola Nicolás. Buenos días.". Vamos a la vista de código de "SaludoWebPart.cs" y creamos una propiedad llamada "Nombre" de tipo string. También tenemos que crear un campo que será el que realmente almacenará la información:

        private string _Nombre;

        public string Nombre
        {
            get { return _Nombre; }
            set { _Nombre = value; }
        }

Tenemos que aplicar a la propiedad "Nombre" los siguientes atributos:
- "System.Web.UI.WebControls.WebParts.Personalizable". Se utiliza para que el "Web Part Manager" persista el valor de la propiedad. Si especificamos un "PersonalizationScope" de "Shared" los cambios que se hagan a la propiedad "Nombre" serán vistos por todos los usuarios (todos compartirán el mismo valor). Si especificamos "User" cada usuario podrá especificar un valor y ese valor será visto sólo por el usuario que lo especificó.
- "System.Web.UI.WebControls.WebParts.WebBrowsable". Si especificamos un valor de "true" la propiedad se mostrará utilizando una interfaz de usuario de edición genérica. La interfaz se crea basándose en el tipo de la propiedad. El objetivo de este artículo es mostrar nuestro propio EditorPart y presentar la propiedad al usuario como nos parezca mejor, por lo que al atributo "WebBrowsable" le vamos a especificar "false".

        private string _Nombre;
        [Personalizable(PersonalizationScope.User)]
        [WebBrowsable(false)]
        public string Nombre
        {
            get { return _Nombre; }
            set { _Nombre = value; }
        }

Para proveer al usuario de una interfaz para que pueda ingresar un nombre tenemos que crear una clase que herede de "System.Web.UI.WebControls.WebParts.EditorPart". Un "EditorPart" se utiliza para editar controles Web Part una vez que el usuario puso el Web Part en modo de edición. Puede haber más de un "EditorPart" por Web Part y estarán en una colección de tipo "EditorPartCollection".

1 – Creamos una clase llamada "SaludoEditorPart".
2 – Cambiamos la declaración de la clase "SaludoEditorPart" para que sea pública.
3 – La hacemos heredar de "System.Web.UI.WebControls.WebParts.EditorPart".

Hay tres métodos principales que tenemos que sobrescribir/implementar:
- "CreateChildControls" – Lo utilizamos para agregar controles al "EditorPart". Los controles que agreguemos aquí le aparecerán al usuario para que pueda editar el Web Part.
- "SyncChanges" (abstract) – El propósito del método es recuperar los valores actuales del control "WebPart" al que se hace referencia en la propiedad "WebPartToEdit" y actualizar los campos del control "EditorPart" con dichos valores para que un usuario pueda editarlos.
- "ApplyChanges" (abstract) – El propósito del método es guardar los valores que ha especificado un usuario para un control "EditorPart" en las propiedades correspondientes del control "WebPart" al que se hace referencia en la propiedad "WebPartToEdit".

using System.Web.UI.WebControls.WebParts;
using System.Web.UI.WebControls;
using System.Web.UI;

namespace WebPartSaludo
{
    public class SaludoEditorPart : EditorPart
    {
        private TextBox _Nombre = null;
        public SaludoWebPart SaludoWebPart
        {
            get { return ((SaludoWebPart)this.WebPartToEdit); }
        }
        public override bool ApplyChanges()
        {
            this.EnsureChildControls();
            this.SaludoWebPart.Nombre = this._Nombre.Text;
            return true;
        }
        public override void SyncChanges()
        {
            this.EnsureChildControls();
            this._Nombre.Text = this.SaludoWebPart.Nombre;
        }
        protected override void CreateChildControls()
        {
            this.Controls.Add(new LiteralControl("Ingrese su nombre:"));
            this._Nombre = new TextBox();
            this._Nombre.Width = new Unit("100%");
            this.Controls.Add(this._Nombre);
        }
    }
}

Una vez que tenemos nuestro "SaludoEditorPart" vamos a la vista de código para "SaludoWebPart.cs". Tenemos que sobrescribir el método "CreateEditorParts" y especificar nuestro "SaludoEditorPart". Además, tenemos que mostrar el saludo al usuario (sobrescribiremos el método CreateChildControls). Toda la clase quedaría así:

using System.Collections.Generic;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI;

namespace WebPartSaludo
{
    public class SaludoWebPart : WebPart
    {
        private string _Nombre;
        [Personalizable(PersonalizationScope.User)]
        [WebBrowsable(false)]
        public string Nombre
        {
            get { return _Nombre; }
            set { _Nombre = value; }
        }
        public override EditorPartCollection CreateEditorParts()
        {
            List<EditorPart> lEditorParts = new List<EditorPart>(1);
            EditorPart lSaludoEditorPart = new SaludoEditorPart();
            lSaludoEditorPart.ID = string.Concat(this.ID, "_SaludoEditorPart");
            lEditorParts.Add(lSaludoEditorPart);
            EditorPartCollection lBaseEditorParts = base.CreateEditorParts();
            return new EditorPartCollection(lBaseEditorParts, lEditorParts);
        }
        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            if (!string.IsNullOrEmpty(this.Nombre))
            {
                this.Controls.Add(new LiteralControl(string.Format("Hola {0}. Buenos días.", this.Nombre)));
            }
        }
    }
}

Desplegar el ensamblado
1 – Compilamos y copiamos "WebPartSaludo.dll" y lo pegamos en la carpeta "Bin" de nuestro directorio virtual de SharePoint para la aplicación a la que le queremos agregar el Web Part (en mi caso "C:\Inetpub\wwwroot\wss\VirtualDirectories\2667\bin").
2 – Abrimos el archivo "web.config" de nuestro directorio virtual de SharePoint para la aplicación a la que le queremos agregar el Web Part (en mi caso "C:\Inetpub\wwwroot\wss\VirtualDirectories\2667\web.config") y bajo "SafeControls" agregamos la siguiente entrada:

<SafeControl Assembly="WebPartSaludo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" Namespace="WebPartSaludo" TypeName="*" Safe="True" />

3 – Ejecutamos "IISRESET".

Mostrando el Web Part
1 – Vamos al sitio Web y en el menú "Acciones del sitio" seleccionamos "Configuración del sitio". Bajo la sección "Galerías" elegimos "Elementos Web". En "Galería de elementos Web" hacemos clic en "Nuevo" y buscamos "WebPartSaludo.SaludoWebPart". Lo marcamos y hacemos clic en "Llenar galería".
2 – Volvemos a la página principal y en el menú "Acciones del sitio" seleccionamos "Editar Página". En la zona que nos interese agregar el Web Part seleccionamos la opción de "Agregar elemento Web". Esto abrirá el catálogo con los Web Parts que tenemos disponibles. Buscamos "SaludoWebPart" que generalmente aparece bajo la sección "Varios" y lo marcamos. Luego hacemos clic en "Agregar".
3 – Una vez que quedó agregado el Web Part a la página, en el menú "editar" seleccionamos "Modificar elemento Web compartido". En el panel "SaludoWebPart" que se abrió a la derecha vamos a ver nuestro "EditorPart". Ingresamos un nombre y hacemos clic en "Aceptar". Salimos del modo edición.

[Haga clic en la imagen para ampliar]

[Haga clic en la imagen para ampliar]

[Haga clic en la imagen para ampliar]

Excel Services: Excel Calculation Services, Excel Web Access y Excel Web Services

2
Filed under WSS 3.0/MOSS 2007

Excel Services permite mostrar y manejar libros de Excel sin que el usuario tenga que instalar controles ActiveX. Está formado por tres componentes: Excel Calculation Services (ECS), Excel Web Access (EWA) y Excel Web Services (EWS). Podemos pensar en Excel Calculation Services como el componente encargado de cargar libros, ejecutar cálculos, en fin, el motor. Excel Web Access convierte libros a HTML permitiendo a usuarios interactuar con ellos. Por último, Excel Web Services permite acceder a libros programáticamente.

Creando una biblioteca de documentos para almacenar libros de Excel
1 – Estando en el sitio que queremos utilizar para mostrar libros de Excel, vamos al menú "Acciones del sitio" y seleccionamos "Configuración del sitio". Bajo la sección "Administración de sitios" elegimos "Bibliotecas y listas del sitio" y por último "Crear nuevo contenido".

[Haga clic en la imagen para ampliar]


2 – Como queremos almacenar documentos de Excel, seleccionamos la opción "Biblioteca de documentos" y la configuramos de la siguiente manera:

[Haga clic en la imagen para ampliar]


3 – Creamos la biblioteca haciendo clic en "Crear". Esta acción nos llevará a la biblioteca que acabamos de crear en donde seleccionaremos la opción "Nuevo documento" que pertenece al menú "Nuevo".

[Haga clic en la imagen para ampliar]


Creando un libro de Excel para exponer en un sitio SharePoint
1 – Luego de haber hecho clic sobre "Nuevo documento", se debería abrir Microsoft Office Excel 2007. Como ejemplo introducimos los siguientes datos:

[Haga clic en la imagen para ampliar]


Para comprobar la "efectividad" de "Excel Calculation Services", los datos en el rango "=Hoja1!$B$1:$B$12" se calculan utilizando la función "ALEATORIO()".

2 – Seleccionamos el rango "=Hoja1!$A$1:$B$12" y en la solapa "Insertar" bajo "Gráficos" insertamos un gráfico.

[Haga clic en la imagen para ampliar]


3 – Seleccionamos el gráfico que acabamos de insertar y en la solapa "Presentación" bajo "Propiedades" le damos un nombre al gráfico de "MiGrafico". Esto es importante ya que después a "Excel Web Access" le vamos a decir que muestre el objeto "MiGrafico" y no todo el libro.

[Haga clic en la imagen para ampliar]


4 – En el botón de Office bajo "Guardar como" seleccionamos "Libro de Excel". De nombre le ponemos "2008.xlsx" y hacemos clic en "Guardar".

[Haga clic en la imagen para ampliar]


Configurando Excel Services
1 – Comprobamos si el servicio "Excel Calculation Services" se encuentra corriendo. Para eso, abrimos "Administración central de SharePoint 3.0" y en la solapa "Operaciones" bajo la sección "Topología y servicios" seleccionamos "Servicios del servidor". En la página "Servicios del servidor" buscamos "Excel Calculation Services" que debería decir "Iniciado". Si no es así, lo iniciamos.

[Haga clic en la imagen para ampliar]


2 – "Excel Services" es un servicio compartido. Para configurarlo tenemos que ir a la herramienta de administración del servicio compartido que la aplicación Web a la que le creamos la biblioteca de documentos tiene asociado. En mi caso, el servicio compartido es "SharedServices1". Accedemos a dicha herramienta haciendo clic sobre "SharedServices1" en el menú de la izquierda.

[Haga clic en la imagen para ampliar]


3 – Una vez que estamos en "Administración de servicios compartidos: SharedServices1", bajo la sección "Configuración de Excel Services" seleccionamos "Ubicaciones de archivos de confianza". Para que "Excel Services" pueda procesar datos, tenemos que especificar una ubicación que nosotros consideremos como segura. Hacemos clic en "Agregar Ubicación de archivo de confianza". Configuramos la sección "Ubicación" de la siguiente manera dejando todo lo demás con sus valores predeterminados. Después, hacemos clic en "Aceptar":

[Haga clic en la imagen para ampliar]

[Haga clic en la imagen para ampliar]


Mostrando el gráfico
1 – Vamos al sitio Web y en el menú "Acciones del sitio" seleccionamos "Editar Página". En la zona que nos interese agregar el gráfico seleccionamos la opción de "Agregar elemento Web". Esto abrirá el catálogo con los Web Parts que tenemos disponibles. Buscamos "Excel Web Access" que generalmente aparece bajo la sección "Datos profesionales" y lo marcamos. Luego hacemos clic en "Agregar".

[Haga clic en la imagen para ampliar]


Atención: si "Excel Web Access" no aparece en el catálogo, una de las posibles causas es que la característica "Características de sitio de Office SharePoint Server Enterprise" no se encuentre activa. Para activarla, en el menú "Acciones del sitio" seleccionamos "Configuración del sitio" y bajo la sección "Administración de la colección de sitios" elegimos "Características de la colección de sitios". Buscamos "Características de colección de sitios de Office SharePoint Server Enterprise" y hacemos clic en el botón "Activar".

2 – Una vez que quedó agregado el Web Part a la página, seleccionamos la opción "Haga clic aquí para abrir el panel de herramientas". Bajo la sección "Visualización de libros" tenemos que configurar "Libro" y "Elemento con nombre". En "Libro" buscamos el documento "2008.xlsx" que guardamos en la biblioteca "Ganancia Anual"("/Ganancia Anual/2008.xlsx"). Como queremos mostrar sólo el gráfico y no todo el libro, en "Elemento con nombre" escribimos "MiGrafico" (sin las comillas) que fue el nombre que le dimos al gráfico. Adicionalmente podemos configurar otros aspectos del Web Part como por ejemplo si queremos que aparezca la barra de herramientas o no, etc. Cuando terminamos hacemos clic en "Aceptar" y luego salimos del modo edición.

[Haga clic en la imagen para ampliar]

[Haga clic en la imagen para ampliar]

[Haga clic en la imagen para ampliar]

Panel de discusión – obtener discusiones y mensajes (Got a Match?)

0
Filed under WSS 3.0/MOSS 2007

A continuación lo que se intenta hacer es interpretar discusiones que pertenezcan a una lista de SharePoint de tipo "Panel de discusión" siguiendo la misma estructura de presentación que nos brinda la vista "Encadenada".

[Haga clic en la imagen para ampliar]


Investigando saqué las siguientes conclusiones:

1 – El tipo de lista "Panel de discusión" por defecto viene con dos tipos de contenido: "Discusión" y "Mensaje".

[Haga clic en la imagen para ampliar]

[Haga clic en la imagen para ampliar]


2 – El tipo de contenido “Discusión” tiene como padre al tipo de contenido “Folder”.
3 – Ambos tipos de contenido comparten las columnas “Asunto” y “Cuerpo”, la diferencia es que el tipo de contenido “Mensaje” tiene la columna “Asunto” oculta ya que no tiene sentido que una respuesta (o mensaje) tenga asunto.
4 – El campo “ThreadIndex” que está oculto y que pertenece a las listas de tipo “Panel de discusión” contiene el nivel al que pertenece un elemento en la jerarquía discusión/mensajes.
5 – La vista “Encadenada” da la sensación de jerarquía utilizando imágenes cuyo ancho podemos calcular así: ((string)pSPListItem[SPBuiltInFieldId.ThreadIndex]).Length – 46.

    <!-- Discusión - Cuerpo de la discusión. -->
    <img height="1" width="0" alt="" src="/_layouts/images/blank.gif" />
    <!-- 1 -->
    <img height="1" width="10" alt="" src="/_layouts/images/blank.gif" />
    <!-- 1.1 -->
    <img height="1" width="20" alt="" src="/_layouts/images/blank.gif" />
    <!-- 1.1.1 -->
    <img height="1" width="30" alt="" src="/_layouts/images/blank.gif" />
    <!-- 2 -->
    <img height="1" width="10" alt="" src="/_layouts/images/blank.gif" />

La estructura "ThreadIndex" de los elementos que aparecen en la primer imagen se ve así:

0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855 – Discusión – Cuerpo de la discusión.
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA85 – 1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA850000016FC8 – 1.1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA850000016FC800000142DF – 1.1.1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF8550000073CDB – 2

Se me ocurrió el siguiente modelo:

[Haga clic en la imagen para ampliar]


Como un panel de discusión por defecto lo único que puede contener son discusiones y mensajes (o respuestas) y dado que una discusión y un mensaje comparten ciertos atributos, la clase "ElementoPanelDeDiscusion" sirve como clase base para "Discusion" y "Mensaje".

Notar que la clase "Discusion" tiene la propiedad "Asunto" mientras que la clase "Mensaje" no la tiene.

La propiedad "Padre" en la clase "Discusion" devuelve null, mientras que en la clase "Mensaje" puede devolver "Discusion" o "Mensaje" (ya que un mensaje puede pertenecer a una discusión o a una respuesta dentro de otra respuesta). Por último, "PanelDeDiscusion" es la clase que hace todo el trabajo.

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;

namespace NicolasFerreira.SharePoint
{
    public abstract class ElementoPanelDeDiscusion
    {
        private string _Cuerpo;

        public string Cuerpo
        {
            get { return _Cuerpo; }
        }
        internal List<Mensaje> _Mensajes;

        public ReadOnlyCollection<Mensaje> Mensajes
        {
            get { return _Mensajes.AsReadOnly(); }
        }

        public abstract ElementoPanelDeDiscusion Padre { get; }

        internal string _ThreadIndex;
        internal ElementoPanelDeDiscusion(string pCuerpo)
        {
            _Mensajes = new List<Mensaje>();
            _Cuerpo = pCuerpo;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Text;

namespace NicolasFerreira.SharePoint
{
    public class Discusion : ElementoPanelDeDiscusion
    {
        private string _Asunto;

        public string Asunto
        {
            get { return _Asunto; }
        }
        internal Discusion(string pCuerpo, string pAsunto)
            : base(pCuerpo)
        {
            _Asunto = pAsunto;
        }

        public override ElementoPanelDeDiscusion Padre
        {
            get { return null; }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Text;

namespace NicolasFerreira.SharePoint
{
    public class Mensaje : ElementoPanelDeDiscusion
    {
        internal ElementoPanelDeDiscusion _Padre = null;

        public override ElementoPanelDeDiscusion Padre
        {
            get { return _Padre; }
        }

        internal Mensaje(string pCuerpo)
            : base(pCuerpo)
        {
        }
    }
}
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace NicolasFerreira.SharePoint.Listas
{
    /// <summary>
    /// Clase encargada de manipular el tipo de lista "Panel de discusión".
    /// </summary>
    public static class PanelDeDiscusion
    {
        /// <summary>
        /// Método fábrica que devolverá un objeto de tipo Discusion o Mensaje dependiendo de T.
        /// </summary>
        /// <typeparam name="T">Tipo de objeto que se desea obtener. El tipo debe heredar de la clase ElementoPanelDeDiscusion.</typeparam>
        /// <param name="pSPListItem">SPListItem que contiene la información necesaria como para crear el tipo de objeto especificado en T.</param>
        private static T CrearElementoPanelDeDiscusion<T>(SPListItem pSPListItem)
             where T : ElementoPanelDeDiscusion
        {
            ElementoPanelDeDiscusion lElementoPanelDeDiscusion = null;
            if (typeof(T) == typeof(Discusion))
            {
                lElementoPanelDeDiscusion = new Discusion((string)pSPListItem[SPBuiltInFieldId.Body],
                    pSPListItem[SPBuiltInFieldId.Title].ToString());
            }
            else if (typeof(T) == typeof(Mensaje))
            {
                lElementoPanelDeDiscusion = new Mensaje((string)pSPListItem[SPBuiltInFieldId.Body]);
            }
            if (lElementoPanelDeDiscusion != null)
            {
                lElementoPanelDeDiscusion._ThreadIndex = ((string)pSPListItem[SPBuiltInFieldId.ThreadIndex]);
            }
            return lElementoPanelDeDiscusion as T;
        }
        /// <summary>
        /// Método que devolverá todas las discusiones que haya en una lista.
        /// </summary>
        /// <param name="pSPListPanelDeDiscusion">SPList que contiene la información necesaria como para obtener todas las discusiones.</param>
        public static List<SPListItem> ObtenerDiscusiones(SPList pSPListPanelDeDiscusion)
        {
            List<SPListItem> lDiscusiones = new List<SPListItem>();
            /*Para obtener todas las discusiones de la lista,
             * tenemos que utilizar la propiedad Folders de SPList.*/
            foreach (SPListItem lSPListItemDiscusion in pSPListPanelDeDiscusion.Folders)
            {
                lDiscusiones.Add(lSPListItemDiscusion);
            }
            return lDiscusiones;
        }
        /// <summary>
        /// Método que devolverá un objeto de tipo Discusion con la discusión (incluyendo mensajes y mensajes anidados).
        /// </summary>
        /// <param name="pSPListItemDiscusion">SPListItem que contiene la información necesaria de la discusión que queremos interpretar.</param>
        public static Discusion ObtenerDiscusion(SPListItem pSPListItemDiscusion)
        {
            Discusion lDiscusion = null;
            /*Para obtener todos los mensajes (incluidos los anidados) lo hago con un SPQuery y establezco
             * la propiedad Folder al valor de pSPListItemDiscusion.Folder que es la carpeta de la discusión.*/
            SPQuery lSPQuery = new SPQuery();
            lSPQuery.Folder = pSPListItemDiscusion.Folder;
            SPListItemCollection lSPListItemCollection = pSPListItemDiscusion.ParentList.GetItems(lSPQuery);
            List<SPListItem> lSPListItemMensajes = new List<SPListItem>();
            foreach (SPListItem lSPListItemMensaje in lSPListItemCollection)
            {
                lSPListItemMensajes.Add(lSPListItemMensaje);
            }
            lSPListItemMensajes.Sort(new ThreadIndexComparer()); //Ordeno los mensajes.
            lDiscusion = CrearElementoPanelDeDiscusion<Discusion>(pSPListItemDiscusion); //Creo la discusión para luego agregar mensajes.
            Mensaje lMensaje = null; //Lo utilizo para tener como referencia el último mensaje que agregué a una discusión o a un mensaje.
            foreach (SPListItem lSPListItemMensaje in lSPListItemMensajes)
            {
                //ThreadIndex contiene el nivel al que pertenece el mensaje.
                string lThreadIndex = ((string)lSPListItemMensaje[SPBuiltInFieldId.ThreadIndex]);
                if (lMensaje != null)
                /*Si anteriormente agregué un mensaje en alguna parte entonces puede que el
                 * nuevo mensaje tenga relación o no con el agregado.*/
                {
                    bool lContinuar = true;
                    while (lContinuar)
                    {
                        if (lThreadIndex.StartsWith(lMensaje._ThreadIndex))
                        /*Verifico si existe una relación
                         * (esto es posible gracias a la estructura del campo ThreadIndex).*/
                        {
                            /*Como existe una relación con el mensaje agregado anteriormente,
                             * entonces el nuevo mensaje es hijo del anterior.*/
                            Mensaje lNuevoMensaje = CrearElementoPanelDeDiscusion<Mensaje>(lSPListItemMensaje);
                            lNuevoMensaje._Padre = lMensaje; //Le indico al nuevo mensaje que su padre va a ser el agregado anteriormente.
                            lMensaje._Mensajes.Add(lNuevoMensaje);
                            lMensaje = lNuevoMensaje; //¡El nuevo mensaje pasa a ser antiguo!
                            lContinuar = false;
                        }
                        else
                        {
                            /*Como no hay relación entre el mensaje agregado anteriormente y el que intento agregar ahora,
                             * me voy moviendo "hacia atrás" en busca de un mensaje que tenga relación
                             * con el que intento agregar.*/
                            if (lMensaje.Padre is Mensaje)
                            {
                                lMensaje = ((Mensaje)lMensaje.Padre);
                            }
                            else
                            {
                                //Ningún mensaje es padre del mensaje que intento agregar, entonces el mensaje es hijo de la discusión.
                                lMensaje = null;
                                lContinuar = false;
                            }
                        }
                    }
                }
                if (lMensaje == null)
                {
                    lMensaje = CrearElementoPanelDeDiscusion<Mensaje>(lSPListItemMensaje);
                    lDiscusion._Mensajes.Add(lMensaje);
                    lMensaje._Padre = lDiscusion;
                }
            }
            return lDiscusion;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace NicolasFerreira.SharePoint
{
    internal class ThreadIndexComparer : IComparer<SPListItem>
    {
        #region IComparer Members

        public int Compare(SPListItem x, SPListItem y)
        {
            string xThreadIndex = ((string)x[SPBuiltInFieldId.ThreadIndex]);
            string yThreadIndex = ((string)y[SPBuiltInFieldId.ThreadIndex]);

            return string.Compare(xThreadIndex, yThreadIndex);
        }

        #endregion
    }
}

[Haga clic en la imagen para ampliar]


Descargar el código.

Limpiar un control Contact Selector Control

0
Filed under WSS 3.0/MOSS 2007

Microsoft Office SharePoint Server 2007 viene con un control ActiveX llamado "Contact Selector Control" que podemos utilizar en formularios de Microsoft Office InfoPath 2007 y que permite a los usuarios especificar y validar usuarios y/o grupos.
Si queremos proveer al usuario con un botón para que pueda eliminar los usuarios y/o grupos que especificó lo que podemos hacer es lo siguiente.

1 – Una vez que tenemos todo configurado para utilizar "Contact Selector Control", en el "Panel de tareas" buscamos "Origen de datos" y seleccionamos la opción "Copiar XPath" del grupo al que el control "Contact Selector Control" está enlazado.

[Haga clic en la imagen para ampliar]


2 – Sobre un botón que tengamos en el formulario nos vamos a las propiedades y elegimos la opción de "Editar código del formulario…"
3 – Creado el manejador para el evento Clicked del botón (en mi caso "CTRL3_5_Clicked") ponemos el siguiente código:

        public void CTRL3_5_Clicked(object sender, ClickedEventArgs e)
        {
            // Escriba aquí su código.
            XPathNavigator lgpContactSelector =
                this.CreateNavigator().SelectSingleNode("Expresión XPath", this.NamespaceManager);

            lgpContactSelector.SetValue(string.Empty);
        }

Al método "SelectSingleNode" le pegamos la expresión XPath que obtuvimos en el primer paso (por ejemplo "/my:misCampos/my:gpContactSelector").