mercoledì 27 agosto 2008
Questo post è nato con uno scopo diverso da quello dell'implementazione di un Rss Reader, dato che, è sufficiente cercare su internet per trovarne di belli e pronti, ma per enfatizzare alcune proprietà molto interessanti dei controlli WPF, come la possibilità di eseguire il raggruppamento dei dati nei controlli ListBox o ListView. Piccola parentesi: per chi volesse conoscere in dettaglio le specifiche RSS 2.0 le può trovare qui. Iniziamo sviluppando un set di classi come riassunto dal seguente Class Diagram:
Nel resto dell'esempio saranno prese in considerazione solo istanze di tipo Rss e RssItem. Generalmente, senza considerare gli elementi opzionali, la struttura xml di un documento RSS è il seguente:
1 <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" ...
2 <channel>
3 <title>Il blog di Pietro Libro</title>
4 <link>http://blogs.ugidotnet.org/PietroLibroBlog/Default.aspx</link>
5 <description />
6 <language>it-IT</language>
7 <copyright>Pietro Libro</copyright>
8 <managingEditor></managingEditor>
9 <generator>Subtext Version 1.9.5.176</generator>
10 ...
11 <item>
12 <title>Adeona, il software &quot;salva&quot; notebook</title>
13 <category>OT</category>
14 <category>Varie ed eventuali</category>
15 <link>http://blogs.ugidotnet.org/PietroLibroBlog/archive/2008/08/26/93820.aspx</link>
16 <description>...</description>
17 ....
18 </item>
Per leggere un tale documento, utilizziamo un'istanza della classe XDocument, presente nel namespace System.Xml.Linq, contenente metodi che permettono la navigazione dei nodi del documento in maniera molto semplice. Se a questo aggiungiamo la potenza di Linq, veramente c'è poco da dire. Prima di tutto creiamo un'istanza di XDocument e leggiamo il documento passando direttamente l'uri del fornitore del feed RSS:
1 XDocument xDocument = new XDocument();
2 xDocument = XDocument.Load(blogUri);
Per recuperare tutti gli elementi item contenuti nel documento, utilizziamo Linq. Per far questo utilizziamo il metodo Descendants passando come parametro la stringa "Item":
1 _items =
2 (from item in xDocument.Descendants("item")
3 select new RssItem
4 {
5 Title = item.Element("title").Value,
6 Description =item.Element ("description").Value,
7 Category = item.Element("category") == null ? "Nessuna Categoria" : item.Element("category").Value ,
8 Link = item.Element("link") == null ? "": item.Element("link").Value
9 }).ToList<RssItem>();
_items è una lista tipizzata di oggetti RssItem.
Per presentare i dati recuperati utilizziamo una Window WPF. Il nostro obiettivo è mostrare i dati raggruppati per categoria, ovvero ottenere qualcosa del tipo:
Per raggiungere il goal, invece di utilizzare la proprietà ItemSource esposta dal controllo ListBox, utilizziamo un'istanza di oggetto di tipo CollectionViewSource la quale altro non è una che un Proxy della classe CollectionView. Quest'ultima permette di eseguire operazioni di raggruppamento, ordinamento, navigazione e ricerca su di una collezione di dati. Definiamo un'istanza di tipo CollectionViewSource nella collezione Window.Resources della nostra Window WPF:
1 <CollectionViewSource x:Key="cvs">
2 <CollectionViewSource.GroupDescriptions>
3 <PropertyGroupDescription PropertyName="Category"/>
4 </CollectionViewSource.GroupDescriptions>
5 </CollectionViewSource>
Così facendo, aggiungiamo alla collezione GroupDescriptions un oggetto di tipo GroupDescription che descrive il criterio in base al quale i dati devono essere raggruppati nella vista, nello specifico in base al valore della proprietà Category.
Tramite codice XAML, andiamo a descrivere come devono essere visualizzati i nostri Gruppi:
1 <Style x:Key="CustomGroupStyle" TargetType="{x:Type GroupItem}">
2 <Setter Property="Margin" Value="0,0,0,5"/>
3 <Setter Property="Template">
4 <Setter.Value>
5 <ControlTemplate TargetType="{x:Type GroupItem}">
6 <Expander IsExpanded="False" BorderBrush="#FFA4B97F" BorderThickness="0,0,0,1">
7 <Expander.Header>
8 <DockPanel>
9 <TextBlock FontWeight="Bold" Text="{Binding Path=Name}"/>
10 <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount,
11 Converter={StaticResource CustomConverter},
12 ConverterParameter=' - Numero di post ({0}) '}"/>
13 </DockPanel>
14 </Expander.Header>
15 <Expander.Content>
16 <ItemsPresenter />
17 </Expander.Content>
18 </Expander>
19 </ControlTemplate>
20 </Setter.Value>
21 </Setter>
22 </Style>
E gli elementi in essi contenuti:
1 <DataTemplate x:Key="ItemTemplate">
2 <StackPanel>
3 <TextBlock Text="{Binding Path=Title}"></TextBlock>
4 <TextBlock>
5 <Hyperlink NavigateUri="{Binding Path=Link}" Click="Hyperlink_Click">Visualizza</Hyperlink>
6 </TextBlock>
7 </StackPanel>
8 </DataTemplate>
Non resta che definire il controllo ListBox:
1 <ListBox Name="lstBox" ItemTemplate="{StaticResource ItemTemplate}" Margin="12,41,12,12">
2 <ListBox.GroupStyle>
3 <GroupStyle ContainerStyle="{StaticResource CustomGroupStyle}"/>
4 </ListBox.GroupStyle>
5 </ListBox>
Utilizzando un controllo Button per recuperare il contenuto del documento RSS, scriviamo il seguente codice C#:
1 LinqRss.Rss rss = new LinqRss.Rss();
2 if (rss.Load("http://blogs.ugidotnet.org/PietroLibroBlog/Rss.aspx"))
3 {
4 CollectionViewSource collectionViewSource = this.FindResource("cvs") as CollectionViewSource ;
5 collectionViewSource.Source = rss.Items;
6 lstBox.ItemsSource = collectionViewSource.View;
7 }
In sintesi, recuperata l'istanza di CollectionViewSource contenuta nelle risorse della nostra Window WPF, impostiamo la proprietà Source dell'oggetto CollectionViewSource utilizzando la collezione Items esposta dall'istanza Rss. Non resta che impostare la proprietà ItemSource del controllo ListBox con la proprietà View esposta da CollectionViewSource. Osservazione: i GroupItem sono costituiti da controlli Expander. Nell'ItemTemplate è presente anche un HyperLink che permette la navigazione verso il post d'interesse.
Codice C# e XAML
martedì 26 agosto 2008
Se avete paura che qualcuno rubi il vostro notebook, non vi resta che installare questo software. E' in versione beta e ha molti punti deboli. ma l'idea è interessante.
domenica 24 agosto 2008
E' tanto che non postavo a riguardo di un gran premio di Formula 1. Problemi di affidabilità per la Ferrari (rottura del motore per Raikkonen), e in questa fase del mondiale non è l'ideale.
Classifica finale del GP d'Europa:
1° Massa (Ferrari), 2° Hamilton (McLaren), 3° Kubica (Bmw Sauber)
Prossimo appuntamento: GP del Belgio, domenica 7 settembre
Supponiamo di avere una situazione schematizzata dal seguente Class Diagram:
Supponiamo di avere un unico metodo Add che aggiunge oggetti di tipo Person ad una lista tipizzata denominata People, come nel caso seguente:
1
2 List<Person> _people = new List<Person>();
3
4 private void Add(Person p)
5 {
6 _people.Add(p);
7 }
Utilizziamo il seguente codice per popolare la collezione People :
1 Employee e = null;
2 e = new Employee();
3 e.Age = 21;
4 e.Name = "Peter";
5 e.Surname = "Book";
6 e.Office = "Piazzale Kennedy 21";
7
8 Add(e);
9
10 e = new Employee();
11 e.Age = 23;
12 e.Name = "Anna";
13 e.Surname = "Rossi";
14 e.Office = "Via Ammiraglio Cagni 36";
15
16 Add(e);
17
18 e = new Employee();
19 e.Age = 35;
20 e.Name = "Mario";
21 e.Surname = "Rossi";
22 e.Office = "Via Duca degli Abruzzi";
23
24 Add(e);
Se volessimo convertire la nostra lista tipizzata di oggetti Person in una lista tipizzata di oggetti Employee:
1 List<Employee> employees = new List<Employee>();
possiamo seguire due strade, la prima è utilizzare un ciclo foreach:
1 foreach (Person p in _people)
2 employees.Add((Employee)p);
La seconda è quella di utilizzare il metodo generico ConvertAll<> che esegue la conversione di un Array di un tipo, Person in questo caso, in un Array di un'altro tipo,Employee, utilizzando un Converter, ovvero un metodo che esegue la conversione di un tipo in un altro. In soldoni (righe di codice :-) ):
Il Converter
1 private Employee PersonEmployeeeConverter(Person p)
2 {
3 if (p.GetType() == typeof(Employee))
4 return (Employee)p;
5 else
6 return null;
7 }
Il codice di conversione:
1 employees = _people.ConvertAll<Employee>(new Converter<Person, Employee>(PersonEmployeeeConverter));
Osservazioni: ConvertAll<> e il concetto di Converter sono stati introdotti con il .Net Framework 2.0
Technorati Tag:
ConvertAll<>
venerdì 22 agosto 2008
Il .Net Framework mette a disposizione degli sviluppatori un numero veramente grande di tool. Questo è un piccolo promemoria per alcuni di questi.
Storeadm.exe- Isolated Storage Tool
Con il .Net Framework 2.0 è stato introdotto il concetto di Isolated Storage. Un utile strumento per leggere o rimuovere tutti gli stores esistenti per un determinato utente è Storeadm.exe. Digitando Storeadm.exe su linea di comando senza specificare nulla, sono visualizzate le opzioni e la sintassi del tool (stesso risutlato può essere ottenuto digitando storeadm /? o storeadm /help). Se volessimo ad esempio visualizzare tutti gli stores sarebbe sufficiente scrivere:
Storeadm.exe /list
Maggiori dettagli su Storeadm
Xsd.exe -XML Schema Definition Tool
Penso che questo tool sia stato utilizzato almeno una volta nella propria vita (professionale) da uno sviluppatore .Net: è fondamentale per la generazione di schemi XML, di classi a partire da file XDR, XML e per la generazione di XSD dai tipi contenuti in un assembly. Le operazioni che possono essere eseguite sono:
- XDR->XSD: Generazione di uno schema XML (XSD) a partire da un XML-Data Reduced schema (XDR)
- XML->XSD: Generazione di uno schema XML (XSD) a partire da un file XML
- XSD->DataSet: Generazione di un dataset a partire da uno file XSD.
- XSD->Classi: Generazione di classi nei linguaggi VB.NET, C# (default), JS o VJS (J#), a partire da un file XSD. Le classi possono essere utilizzate da un Systemt.XML.Serialization.XMLSerializer per leggere e scrivere codice XML secondo lo schema dato
- Classi->XSD: Generazione a partire da uno o più tipi in un assembly del corrispondente XML schema.
Maggiori dettagli su Xsd.exe
Sn.Exe - Strong Name Tool
Anche in questo caso penso che uno sviluppatore .Net abbia utilizzato questo tool almeno una volta. In particolare, permette di firmare un assembly con uno strong name (nome sicuro), assicurando che il nome assegnato all'assembly sia univoco. In particolare uno strong name soddisfa i seguenti requisiti:
- Univocità dei nomi perchè basati su coppie di chiavi univoche. Nessun'altro può generare lo stesso nome per un assembly diverso.
- Garanzia sul fatto che nessun'altro può produrre una versione successiva del proprio assembly. Questo garantisce agli utenti che utilizzano una nuova versionie dell'assembly che quest'ultima è stata prodotta dalla stessa fonte del precedente
- Controllo d'integrità garantendo che il contenuto dell'assembly non sia stato modifcato successivamente alla sua creazione.
Questo tool espone numerose opzioni. Vale la pena guardare anche i vari esempi di utilizzo presenti nella documentazione MSDN.
Maggiori dettagli su Sn.exe
SignTool.Exe - File Signing Tool
Questo tool permette di aggiungere una firma digitale ad un file, verificare una firma già presente o aggiungere un Time Stamp. Per questo tool è richiesta l'installazione della versione ridistribuibile di CAPICOM 2.0. Anche in questo caso le opzioni offerte sono numerose scondo del tipo di operazione da eseguire.
Maggiori dettagli su SignTool.exe
Caspol.exe - Code Access Security Policy Tool
Questo tool permete all'utente e agli amministratori di modificare le policy di sicurezza a livello machine, user ed enterprise. L'intersezione dell'insieme delle autorizzazioni forniti da questi tre criteri è l'insieme di autorizzazioni che sono fornite ad un assembly. Strutturalmente, ogni criterio ha una gerarchia di gruppi di codice, dove ognuno di questi rappresenta una condizione di appartenenza che determina quale codice appartiene a tale gruppo. A ogni gruppo è associato un insieme di autorizzazioni che specifica a runtime quali autorizzazioni sono concesse al codice che soddisfa la condizione di appartenenza. Queste poche righe non possono assolutamente esaurire il significato e l'utilità di questo importante strumento, ma è necessaria una più attenta lettura.
Maggiori dettagli su Caspol.exe
Permview.exe - Permission View Tool
Questo strumento permette di visualizzare le autorizzazioni minime, facoltative, rifiutate e richeste da un assembly. Questo tool può essere utilizzato anche per visualizzare la protezione dichiarativa di un assembly. Permview.exe è disponibile nelle versioni 1.0 e 1.1 del .Net Framework. Nella versione .Net 2.0 e superiori è presente il tool PermCalc.exe.
Maggiori Dettagli su PermView.exe
Maggiori Dettagli su PermCalc.exe
mercoledì 20 agosto 2008
Se volete seguire in diretta le finali di Pechino 2008: Yalp!
Technorati Tag:
Pechino 2008
lunedì 18 agosto 2008
Promemoria. Questo link presenta una possibile soluzione nel caso in cui, lavorando con SQL Server 2005, si presenti il seguente messaggio d'errore:
The database principal owns a schema in the database, and cannot be dropped. (Microsoft SQL Server, Error: 15138).
Piccolo post derivato da una risposta su un forum. Un semplice modo per dotare una nostra applicazione di un log degli eventi, è utilizzare la classe EventLog che il .Net Framework mette a disposizione. MSDN suggerisce, nel caso in cui si debba usare il log per poche operazioni di utilizzare i metodi statici della classe EventLog, altrimenti conviene registrare un log degli eventi personalizzato e utilizzare un'istanza della classe EventLog. Quando detto si traduce in poche righe di codice:
1 if (!EventLog.SourceExists("MyEventLog", "."))
2 EventLog.CreateEventSource("MyEventLog", "CustomApplication");
3
4 _demoLog = new EventLog("CustomApplication", ".", "MyEventLog");
Per aggiungere una nuova entry al log è sufficiente utilizzare il metodo WriteEntry:
1 _demoLog.WriteEntry("Evento click eseguito", EventLogEntryType.SuccessAudit);
Se ad esempio, volessimo visualizzare le entries del nostro log in un controllo ListBox è sufficiente eseguire un ciclo for each sulla collezione Entries esposta dall'istanza di EventLog:
1 string entryMessage ="";
2 foreach (EventLogEntry entry in _demoLog.Entries ){
3 entryMessage = string.Format ("{0} {1} {2}",
4 entry.EntryType.ToString (),entry.MachineName ,
5 entry.Message );
6
7 listBox1.Items.Add (entryMessage );
8 }
Per cancellare le entries, utilizziamo il metodo Clear() dell'istanza:
Nel caso in cui invece volessimo eliminare l'Event Log registrato, è sufficiente utilizzare il metodo statico Delete() :
1 EventLog.Delete("MyEventLog")
Importante (1): Per creare ed eliminare un event source ( e realtive entries) è necessario che l'utente abbia i diritti di amministrazione, altrimenti a run-time verrà sollevata l'opportuna eccezione. Magari il log degli eventi potrebbe essere registrato sulla macchina in fase di installazione dell'applicazione, fase in cui ci si aspetta che l'utente che esegue il task sia in posseso degli opportuni diritti di amministrazione
Importante (2): Per utilizzare il sistema di logging è necessario che il sistema operativo che ospita l'applicazione sia Windows 2000, Windows XP, Windows 2003 Server o superiori (Windows 98 e Windows Millennium Edition non supportano l'Event log)
Oltre che da codice, possiamo visualizzare lo stato del nostro log utilizzando l'apposito Event Viewer richiamabile anche da riga di comando: eventvwr.exe
Per l'esempio indicato, su Windows Vista Ultimate, otteniamo:
Per approfondimenti, MSDN
domenica 17 agosto 2008
Se in una nostra WPF Windows Application abbiamo la necessità di utilizzare il nuovo controllo WebBrowser per visualizzare del codice HTML, possiamo percorrere due strade, secondo delle nostre esigenze:
1) Il codice HTML deve essere visualizzato (ad esempio) sull'evento click di un controllo button: possiamo utilizzare i metodi NavigateToString(string text) o NavigateToStream(System.IO.Stream stream). Ad esempio, se scriviamo:
1 WebBrowserInstance.NavigateToString("<h2>Prova</h2><p>Ciao</p>");
otteniamo:
dove il codice XAML è:
1 <WebBrowser ClipToBounds="True" Name="WebBrowserInstance"></WebBrowser>
2) Se il codice HTML da visualizzare è ottenuto dinamicamente, sembra non essere presente (attualmente) un modo diretto per bindare il codice HTML ad una proprietà del controllo WebBrowser (anche se quest'ultimo è un DependencyObject). Per ovviare al problema possiamo creare un nuovo UserControl contenente un controllo WebBrowser. Il codice XAML dell'UserControl è:
1 <UserControl x:Class="MyNamespace.BindableWebBrowser"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 Height="300" Width="300" Loaded="UserControl_Loaded">
5 <Grid Name="contentGrid">
6 </Grid>
7 </UserControl>
8
Mentre il codice C# è:
1 namespace MyNamespace
2 {
3 public partial class BindableWebBrowser : UserControl
4 {
5
6 private WebBrowser _wb = null;
7
8 public WebBrowser Wb
9 {
10 get { return _wb; }
11 set { _wb = value; }
12 }
13
14 public BindableWebBrowser()
15 {
16 InitializeComponent();
17
18 _wb = new WebBrowser();
19 _wb.Margin = new Thickness(0);
20 contentGrid.Children.Add(_wb);
21 }
22
23 public string HtmlCode
24 {
25 get { return (string)GetValue(TextProperty); }
26 set { SetValue(TextProperty, value); }
27 }
28
29 public static readonly DependencyProperty TextProperty =
30 DependencyProperty.Register("HtmlCode", typeof(string),
31 typeof(BindableWebBrowser), new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(HtmlDocumentChangedCallBack)));
32
33 public static void HtmlDocumentChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
34 {
35 BindableWebBrowser bindableWebBrowser = (BindableWebBrowser)property;
36 bindableWebBrowser.Wb.NavigateToString((string)args.NewValue);
37 }
38
39 private void UserControl_Loaded(object sender, RoutedEventArgs e)
40 {
41 }
42 }
43 }
In questo modo in una Window WPF, nel codice XAML, possiamo scrivere:
1 <local:BindableWebBrowser HtmlCode="<h3>Hello World!!!</h3>" Margin="24.577,60,12,0" Grid.Column="1" Height="65" VerticalAlignment="Top"></local:BindableWebBrowser >
oppure eseguire un binding del controllo in questo modo:
1 <local:BindableWebBrowser HtmlCode="{Binding Path=Description}" ></local:BindableWebBrowser >
Ottenendo, utilizzando il codice del primo caso, quanto segue:
Se invece il nostro scopo è semplicemente visualizzare un sito web, allora è sufficiente scrivere nel codice XAML:
1 <WebBrowser Source="http://blogs.ugidotnet.org/" ClipToBounds="True" Name="wbTest"></WebBrowser>
Senza scrivere un nuovo UserControl.
L'aspetto grafico dell'UserControl può essere notevolmente migliorato (parliamo di WPF :-)), ma spero che il codice possa ritornare utile.
Technorati Tag:
WPF,
WebBrowser
giovedì 14 agosto 2008
Potrebbe sembrare uno scherzo, ma non lo è...Gordon è un robot che schiva gli ostacoli, ma ha un cervello biologico. Per chi è interessato, qui l'articolo completo.