Monday, February 16, 2009

Link .Net #15

. Monday, February 16, 2009
0 commenti

Quindicesimo appuntamento con Link .Net:

WPF : Circular Progress Bar

Stanchi della solita progress bar? C’è chi si è divertito nel realizzare uno UserControl in WPF a forma circolare. en

Adding SharePoint Intellisense to Visual Studio 2008
Come abilitare l'intellisense per SharePoint utilizzando Visual Studio 2008. en

SQL SERVER - Simple Example of Reading XML File Using T-SQL
Un semplice tutorial su come leggere tramite T-SQL un file XML. en

Microsoft Recite
Progetto Microsoft per Windows Mobile che consente di registrare note vocali. Da provare. en

Read More »»

Saturday, February 14, 2009

Fare un pò di spazio nel GridView

. Saturday, February 14, 2009
0 commenti

I clienti si sà non sono semplici da accontentare. Sempre più spesso mi capita di sentire per web application intranet che amministratori e non solo, devono poter visionare venti o più colonne in una griglia, nonostante la possibilità di generare report in Excel o la possibilità di utilizzare custom applicazioni sviluppate tramite VSTO. Come si può facilmente immaginare oltre alla dubbia utilità, c’è comunque un problema legato alla leggibilità dei dati.
E’ anche vero che è possibile trovare controlli, che tra le tante feature, permettono di ridimensionare a piacimento la dimensione delle righe/colonne oppure poter cambiare la posizione delle colonne all’interno della griglia ecc.
Qualche esempio:
RadGrid della Telerik
jqGrid
AJAX Grid della Ccomponentart
Ma volendo qualcosa si può facilmente creare con pochissimo sforzo.
Un’idea potrebbe essere quella di poter “rimuovere” la colonna desiderata, cliccando semplicemente sull’header di essa, per poi “rinserirla” tramite un apposito pulsante.


SchemaVisibilityGrid
Per semplicità il controllo GridView è composto da quattro TemplateColumn:

   1: <table cellpadding="2" cellspacing="2" width="100%">
   2:   <tr>
   3:     <td align="right">
   4:       <asp:Panel ID="pnlBox" runat="server">
   5:         <table>
   6:           <tr>
   7:             <td><img id="imgCollapse" src="img/plus.gif" alt="Collapse" style="cursor:pointer" 
onclick="modifiedImage()"/></td>
   8:           </tr>
   9:           <tr>
  10:             <td><asp:Panel ID="pnlColumns" runat="server" style="display:none"></asp:Panel></td>
  11:           </tr>
  12:         </table>
  13:       </asp:Panel>
  14:     </td>
  15:   </tr>
  16:   <tr>
  17:     <td>
  18:       <asp:GridView ID="dgrDelete" runat="server" AutoGenerateColumns="false" Width="100%" 
OnPreRender="dgrDelete_PreRender" CssClass="GridViewStyle">
  19:         <Columns>
  20:           <asp:TemplateField HeaderText="Col1">
  21:             <ItemTemplate>
  22:               <asp:Label ID="lblCol1" runat="server" Text='<%#Eval("Col1") %>'></asp:Label>
  23:             </ItemTemplate>
  24:           </asp:TemplateField>
  25:           <asp:TemplateField HeaderText="Col2">
  26:             <ItemTemplate>
  27:               <asp:Label ID="lblCol2" runat="server" Text='<%#Eval("Col2")%>'></asp:Label>
  28:             </ItemTemplate>
  29:           </asp:TemplateField>
  30:           <asp:TemplateField HeaderText="Col3">
  31:             <ItemTemplate>
  32:               <asp:Label ID="lblCol3" runat="server" Text='<%#Eval("Col3")%>'></asp:Label>
  33:             </ItemTemplate>
  34:           </asp:TemplateField>
  35:           <asp:TemplateField HeaderText="Col4">
  36:             <ItemTemplate>
  37:               <asp:Label ID="lblCol4" runat="server" Text='<%#Eval("Col4")%>'></asp:Label>
  38:             </ItemTemplate>
  39:           </asp:TemplateField>
  40:         </Columns>
  41:         <RowStyle CssClass="RowStyle" />
  42:         <HeaderStyle CssClass="HeaderStyle" />
  43:         <AlternatingRowStyle CssClass="AltRowStyle" />
  44:       </asp:GridView>
  45:     </td>
  46:   </tr>
  47: </table>

Gli elementi principali del markup appena visto sono il controllo GridView e un Panel. Quest’ultimo con id pnlColumns può essere minimizzato tramite l’immagine con id imgCollapse.
Sull’evento PreRender del controllo GridView aggiungo per ogni intestazione di colonna l’evento onclick e l’attributo id tramite la collection Attributes:

   1: protected void dgrDelete_PreRender(object sender, EventArgs e)
   2: {
   3:   if (dgrDelete.HeaderRow != null && dgrDelete.HeaderRow.Cells.Count > 0)
   4:   {
   5:     int count = 0;
   6:     foreach (TableCell cell in dgrDelete.HeaderRow.Cells)
   7:     {
   8:       cell.Attributes.Add("id", "th" + count.ToString());
   9:       cell.Attributes.Add("onclick", "removeColumns(" + count.ToString() + ")");
  10:      count += 1;
  11:     }
  12:   }
  13: }

L’id di ogni header di colonna è composto dalla concatenazione di due elementi: “th” e un indice di posizione. Sull’evento onclick viene invece, richiamata la funzione removeColumns passando come unico paramentro l’indice, che come abbiamo visto in precedenza compone parte dell’id dell’intestazione. In questo modo possiamo facilmente recuperare la colonna che si desidera “rimuovere”.

Non ci resta che dare uno sguardo alla parte, forse più interessante, riguardante JavaScript. Con l’aiuto del fido JQuery partiamo dall’analizzare la funzione removeColumns:

   1: //Rendo non visibile la colonna selezionata nella griglia
   2: function removeColumns(index) {
   3:   $("#dgrDelete tbody tr").each(function(n) {
   4:     this.cells[index].style.display = 'none';
   5:   });
   6:   var title = $("#th" + index + "").text();
   7:   $("#th" + index + "").css("display", "none");
   8:   addBookmarksColumn(title, index);
   9: }

Per ogni riga che compone la griglia (vedere riga 3) imposto per la cella avente l’indice passato come parametro alla funzione, la proprietà display a none, cioè viene resa invisibile (riga 4). Stessa sorte spetta anche alla cella di instestazione della colonna (riga 7), ma prima recupero il testo contenuto (riga 6). Tale testo insieme all’indice, costituiscono i parametri della funzione addBookmarksColumn.

   1: //Aggiunge al panel info sulla colonna rimossa dalla griglia
   2: function addBookmarksColumn(header, index) {
   3:   $('#pnlColumns').append("<div id=\"div" + header + "\">");
   4:   $('#pnlColumns').append("<span id=\"sp" + header + "\" >" + header + " </span>");
   5:   $('#pnlColumns').append("<img id=\"img" + header + "\" style=\"cursor:pointer\" 
src=\"img/add.gif\" onclick=\"addColumns("
+ index + ")\" />");
   6:   $('#pnlColumns').append("</div>");
   7: }

Al pannello pnlColumns viene aggiunto a runtime del codice HTML. In pratica ogni volta che viene “rimossa” una colonna, questo pannello si popola di un tag div composto a sua volta da un tag span contenente il testo dell’header, più un’immagine che consente all’utente di rendere nuovamente visibile la colonna all’interno della griglia.
(NB: Ogni elemento presenta l’attributo id composto in parte dal testo della cella di intestazione.)
Sull’onclick dell’immagine di add viene richiamata la funzione addColumns:

   1: //Rendo visibile la colonna selezionata nella griglia
   2: function addColumns(index) {
   3: $("#dgrDelete tbody tr").each(function(n) {
   4:   if ($.browser.msie)
   5:     this.cells[index].style.display = 'block';
   6:   else
   7:     this.cells[index].style.display = 'table-cell';
   8:   });
   9:   var title = $("#th" + index + "").text();
  10:   if ($.browser.msie)
  11:     $("#th" + index + "").css("display", "block");
  12:   else
  13:     $("#th" + index + "").css("display", "table-cell");
  14:   removeBookmarksColumn(title);
  15: }

Essenzialmente questa runtine si comporta in modo opposto alla funzione removeColumns, in quanto rende visibile la colonna.
La differenza sostanziale riguarda il valore assegnato alla proprietà display. Poichè non tutti i browser presentano la stessa compatibilità con questa proprietà, eseguo un controllo sul tipo di browser (riga 4 e 10).
In caso di browser come Chrome, Firefox, Opera utilizzo il valore table-cell per una corretta visualizzazione delle celle (riga 7 e 13), nel caso invece di IE utilizzo il valore block.
Non rimane che ripulire il pannello dagli elementi che si riferiscono alla colonna che abbiamo reso visibile tramite la funzione removeBookmarksColumn:

   1: //Rimuove gli elementi che identificano la colonna nel panel
   2: function removeBookmarksColumn(header) {
   3:   $("#img" + header + "").remove();
   4:   $("#sp" + header + "").remove();
   5:   $("#div" + header + "").remove();
   6: }

Infine l’utente può “collassare” il pannello tramite l’immagine con id imgCollapse:
   1: //Apre o chiudi il panel contenente le colonne rimosse
   2: function modifiedImage() {
   3: if ($('#imgCollapse').attr('src') == "img/plus.gif") {
   4:     $('#pnlColumns').show('slow');
   5:     $('#imgCollapse').attr('src', 'img/min.gif');
   6:   }
   7: else {
   8:     $('#pnlColumns').hide('slow');
   9:     $('#imgCollapse').attr('src', 'img/plus.gif');
  10:   }
  11: }

Oltre alla funzione di minimizzazione viene modificato il valore dell’attributo src dell’immagine (add, min).
Il codice come ho già detto in altri post, si può notevolmente migliorare, con ulteriori controlli e personalizzazioni. Per concludere alcune immagini:
(NB: lo stile applicato alla griglia lo potete trovare al seguente indirizzo: Style GridView.)
VisibilityGrid1
VisibilityGrid2 
VisibilityGrid3

Read More »»

Sunday, February 8, 2009

PagerTemplate per GridView

. Sunday, February 8, 2009
0 commenti

Molto spesso mi capita di vedere griglie che mostrano l’intero set di dati senza fornire un accesso tramite paginazione. Personalmente credo che offrire solo un sottinsieme di record sia fondamentale.
Premetto che l'’intento di questo post non è quello di parlarvi della paginazione, dei benefici che questa può portare all’applicazione, nè tantomeno realizzare un custom paging.
A tal riguardo trovate moltissimo materiale in rete, giusto per citare qualche fonte:
ASP.NET Custom Paging with GridView using ObjectDataSource
GridView Paging using ASP.NET AJAX Slider Extender

Quello che invece cercherò di illustrare è come sia possibile personalizzare il paging del controllo GridView utilizzando il property PagerTemplate.
Ma partiamo con ordine, descrivendo a grandi linee l’idea: unire un paging a una scrollbar. Tramite i due classici pulsanti next e prev sarà possibile spostare la scrollbar del paginatore, permettendo all’utente di aumentarne la velocità di spostamento restando semplicemente con il cursore del mouse sui relativi pulsanti:
Paging2
Il tutorial presenta in parti uguali codice Asp.Net e JavaScript.
Partiamo dal markup del controllo GridView:

   1: <table cellpadding="0" cellspacing="0" border="0">
   2:   <tr>
   3:     <td><img src="img/ang_top_sn.jpg" /></td>
   4:     <td background="img/top.jpg"></td>
   5:     <td><img src="img/ang_top_dx.jpg" /></td>
   6:   </tr>
   7:   <tr>
   8:     <td background="img/fondo_sn.jpg"></td>
   9:     <td>
  10:       <asp:GridView ID="dgrTest" runat="server" AllowPaging="true" PageSize="5" Width="750px">
  11:         <RowStyle BackColor="#999999" />
  12:           <PagerTemplate>
  13:             <table width="100px" align="center">
  14:               <tr>
  15:                 <td width="24" align="left">
  16:                   <img id="prev" src="img/minus.jpg" width="24" height="24" onmouseover="fnPrev()" 
onmouseout="stop()" />
  17:                 </td>
  18:                 <td>
  19:                   <div id="divScroll" 
style="width: 200px; height: 40px; overflow-x: hidden; overflow-y: hidden">
  20:                     <asp:Table ID="tbItems" runat="server"></asp:Table>
  21:                   </div>
  22:                 </td>
  23:                 <td width="24" align="right">
  24:                   <img id="next" src="img/plus.jpg" width="24" height="24" onmouseover="fnNext()" 
onmouseout="stop()" />
  25:                 </td>
  26:               </tr>
  27:             </table>
  28:           </PagerTemplate>
  29:         <AlternatingRowStyle BackColor="#CCCCCC" />
  30:       </asp:GridView>
  31:     </td>
  32:     <td background="img/fondo_dx.jpg"></td>
  33:   </tr>
  34:   <tr>
  35:     <td><img src="img/ang_bott_sn.jpg" /></td>
  36:     <td background="img/bott.jpg"></td>
  37:     <td><img src="img/ang_bott_dx.jpg" /></td>
  38:   </tr>
  39: </table>


Questo markup produce il seguente risultato:Paging1

Nel PagerTemplate sono presenti principalmente quattro elementi.
Un div che tramite la proprietà overflow ci consente di gestire il suo contenuto al superamento dei limiti imposti dai valori assegnati alle proprietà width e/o height.

La tabella tblItems targata runat server contenente i numeri di pagina.
Infine alle due estremità del div sono presenti due immagini (con segno più e con segno meno) che consentono lo spostamento della scrollbar in entrambi i sensi.

Il codice lato server per popolare la tabella con id tblItems è abbastanza semplice. Consiste nel creare un numero di celle, pari al numero totale di pagine. In ogni cella viene aggiunto a runtime un controllo LinkButton:


   1: private void CreatePager()
   2: {
   3:   Table tbItems = (Table)dgrTest.BottomPagerRow.Cells[0].FindControl("tbItems");
   4:   if (tbItems != null)
   5:   {
   6:     TableRow row = new TableRow();
   7:     TableCell cell;
   8:     for (int cntPage = 1; cntPage <= dgrTest.PageCount; cntPage++)
   9:     {
  10:       LinkButton lnkItem = new LinkButton();
  11:       lnkItem.ID = "page" + cntPage.ToString();
  12:       lnkItem.Click += new EventHandler(lnkItem_Click);
  13:       lnkItem.Text = cntPage.ToString();
  14:       //Elemento corrente
  15:       if (dgrTest.PageIndex + 1 == cntPage)
  16:         //Layout link selezionato
  17:         lnkItem.BackColor = System.Drawing.Color.Yellow;
  18:       cell = new TableCell();
  19:       cell.Style.Add(HtmlTextWriterStyle.TextAlign, "center");
  20:       cell.Controls.Add(lnkItem);
  21:       row.Cells.Add(cell);
  22:     }
  23:     tbItems.Rows.Add(row);
  24:     tbItems.Width = Unit.Pixel(row.Cells.Count * 30);
  25:   }
  26: }


Sull’evento click dei LinkButton (necessario per spostarci di pagina):
   1: void lnkItem_Click(object sender, EventArgs e)
   2: {
   3:   dgrTest.PageIndex = Convert.ToInt32(((LinkButton)sender).Text) - 1;
   4:   //Rieseguire il Bind
   5:   CreatePager();
   6: }

Ma vediamo come il codice JavaScript si integra con quanto visto fin'ora.
Per comodità e per un rapido sviluppo userò il Framework JQuery.

Partiamo dall'implementazione dei due eventi: omouseover e onmouseout presenti sulle immagini next e prev.
Sull'omouseover dell'immagine con id next viene richiamata la funzione fnNext. Quest'ultima a sua volta, richiama il metodo: setInterval(), che permette di creare intervalli prestabiliti nell'arco di una routine:

   1:  
   2: function fnNext() {
   3:   scrollTimer = setInterval(speedNext, 10);
   4: }

Il primo argomento del metodo rappresenta la funzione da eseguire, mentre il secondo sono i millisecondi di attesa. La variabile globale scrollTimer è importante perchè ci consente di interrompere l'esecuzione della funzione richiamando il metodo clearInterval.
Quest'ultimo viene innescato allo scatenarsi dell'evento onmouseover:

   1: function stop() {
   2:   clearInterval(scrollTimer);
   3:   speed = 5;
   4: }
La logica di spostamento è concentrata nelle due funzioni speedNext e speedPrev.
Iniziamo dalla funzione speedNext:
   1: var scrollTimer;
   2: var speed = 5;
   3: function speedNext() {
   4:   if ($('#divScroll').scrollLeft() != ($('#divScroll table[id$=tbItems]').width() - $('#divScroll').width())) {
   5:     document.getElementById("divScroll").scrollLeft += speed;
   6:     speed += 1;
   7:     $('#valueScrollLeft').val($('#divScroll').scrollLeft());
   8:   }
   9: }

Tramite la prima istruzione (riga numero 4) posso verificare se la barra di scorrimento ha raggiunto il limite massimo.

E’ stato necessario assegnare una dimensione fissa alla tabella contenente i numeri di pagina (vedere riga numero 24 del codice lato server).
Supponendo che la tabella contenuta nel div abbia una larghezza di 200 px e che il div sia invece di 100 px, il valore massimo che potrà essere assegnato alla proprietà scrollLeft risulterà uguale a 200px - 100px = 100px.
Nella seconda istruzione (riga numero 5) compare un'altra variabile globale: speed.
Questa inizialmente valorizzata a 5, viene di volta in volta incrementata di 1 (vedere istruzione riga 6). Indica la posizione corrente della scrollbar (vedere istruzione riga 5).
Invece l’immagine con id prev, tramite la funzione speedPrev, implementa un comportamento opposto a quello appena descritto:

   1: function speedPrev() {
   2:   if ($('#divScroll').scrollLeft() != 0) {
   3:     document.getElementById("divScroll").scrollLeft -= speed;
   4:     speed += 1;
   5:     $('#valueScrollLeft').val($('#divScroll').scrollLeft());
   6:   }
   7: }

La posizione iniziale è uguale a 0 (riga 2), ed inoltre il valore assegnato alla proprietà scrollLeft invece di essere incrementato viene decrementato (riga 3). 
Infine per evitare che ad ogni postback si perda la posizione della scrollbar, il valore della proprietà scrollLeft viene memorizzato in un campo hidden. Così facendo posso settare correttamente la proprietà (il controllo avviene ad caricamento della pagina):

   1: $(document).ready(function() {
   2:   if ($('#valueScrollLeft').val() != "")
   3:     $('#divScroll').scrollLeft($('#valueScrollLeft').val());
   4: });

Markup del campo hidden:
   1: <asp:HiddenField ID="valueScrollLeft" runat="server" />

Concludo dicendo che come al solito questo mio post non vuole essere un punto di arrivo ma bensì di partenza.
Buono studio ;)

Read More »»