Saturday, September 10, 2011
Leggendo per caso il blog di Gianni Giaccaglini (http://blogs.wpfitalia.it/GianniGiaccaglini/archive/2011/09/08/Binding-ADO-tramite-classe-ad-hoc.aspx) riguardo il binding verso dataset, ho notato un capitolo, “INotifyPropertyChanged server davvero?” e mi sono ricordato di un problema simile (forse lo stesso) che mi è capitato tempo fa.
Iniziamo dal codice xaml e il datacontext:
<StackPanel>
<TextBox Text="{Binding Path=PrimaProp}" />
<TextBox Text="{Binding Path=PrimaProp}" />
<TextBox Text="{Binding Path=SecondaProp}" />
<TextBox Text="{Binding Path=SecondaProp}" />
<Button Content="Cambia" Click="Button_Click" />
</StackPanel>
public class MyDataContext
{
public string PrimaProp { get; set; }
public string SecondaProp { get; set; }
}
Qualcuno noterà che MyDataContext (nel codebehind viene impostata un istanza come DataContext) non implementa INotifyPropertyChanged.
Modificando una textbox ci si aspetterebbe che le altre non cambino perchè non si notifica alla vista che la proprietà è stata cambiata… E invece tutto funziona!
Ai tempi pensai che l’engine di wpf fosse abbastanza intelligente da usare un oggetto binding uguale se viene scritto uguale in più parti… invece no
.
Se proviamo infatti a impostare via codice il binding (es: textbox1.SetBinding(TextBox.TextProperty, new Binding(“PrimaProp”))), con nuove istanze di Binding su diversi oggetti, continua ancora a funzionare.
Proviamo a cambiare la proprietà del datacontext da codice tramite il bottone con:
(this.DataContext as MyDataContext).SecondaProp=”modifica da codice”… NON FUNZIONA!
Non sapendo dove andare a parare, ho cercato con google, e infine ho trovato la soluzione indicata qui: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/9365bb6a-b411-4967-9a03-ae2a810fb215/
Sunto della soluzione:
- Se un oggetto CLR implementa INotifyPropertyChanged, è cura del developer lanciare l’evento NotifyPropertyChanged
- Se un oggetto CLR NON implementa INotifyPropertyChanged, il binding lavora sul TypeDescriptor dell’oggetto. Esso va a registrare alla modifica delle proprietà tramite PropertyDescriptor.AddValueChanged().
Quindi:
1) come far andare il codice precedente riguardo il bottone:
”semplice” (?!?) basta recuperare la PropertyDescriptor dell’oggetto e impostare così la proprietà:
var propertyDescriptor = TypeDescriptor.GetProperties(this.DataContext).Find("SecondaProp", false);
propertyDescriptor.SetValue(this.DataContext, "ModificaDaCodice");
2) come far smettere di andare il precedente esempio:
Implementiamo l’interfaccia INotifyPropertyChanged e propaghiamo l’evento solo per PrimaProp. Questa funziona, ma SecondaProp no
.
Performance (mentre l’ho fatto ho sentito l’esigenza di un altro post)
Se eseguo il codice nel thread UI, le performance sono molto simili.
Se eseguo il codice in un altro thread, l’utilizzo di INotifyPropertyChanged risulta più rapido rispetto a PropertyDescriptor.
Conclusioni:
Data la flessibilità e la maggior facilità implementativa, la mia preferenza ricade indubbiamente a INotifyPropertyChanged. Le performance inoltre sembrerebbero avvalorare tale scelta. Quindi Me.NofifyPropertyChanged(“you”);
Tuesday, August 09, 2011
Oggi mi sono imbattuto in questo fastidioso problema: avete mai usato uno slider? se sì, avete mai messo in Binding le dependency property Minimum e Maximum oltre che a Value? vi è andato al primo colpo? fortunati …
Il problema principale è l’ordine con cui si imposta. In ordine si dovranno impostare i binding per:
Se così non fate, Silverlight si perde qualcosa per strada
. Una corretta dichiarazione è quindi:
<Slider Maximum="{Binding Path=Max}" Minimum="{Binding Path=Min}" Value="{Binding Path=Value, Mode=TwoWay}" />
Se, come me, provate a impostare prima Minimum e poi Maximum, noterete che il valore di Maximum non viene impostato (anzi, se debuggate Maximum verrà impostato a zero).
Spero non perdiate tutto il tempo che io ho perso per capire il problema.
ps: in WPF4 è order independent.
Wednesday, June 01, 2011
Oggi si è presentato il seguente problema:
Ho usato una griglia per applicare una sorta di filtro su un altro controllo in base alle DataGridRow selezionate; il cliente ha voluto che, cliccando fuori dalle colonne di una DataGrid, tutte le righe selezionate si deselezionassero (eliminando in questo modo il filtro).
L’unico modo navito per farlo è cliccare sulla DataGridRow selezionata tenendo premuto il tasto CTRL.
Ho quindi creato il codice qui sotto.
In pratica cliccando al di fuori delle colonne (parte destra della Griglia) si clicca su un elemento figlio di una DataGridRow ma non su un elemento figlio di una DataGridCell (si può vedere facilmente con WPF Inspector oppure con Snoop).
Testo quindi questa condizione e deseleziono tutto se verificata:
public static class DataGridBehaviors
{
public static bool GetUnselectOnClickOutside(DependencyObject obj)
{
return (bool)obj.GetValue(UnselectOnClickOutsideProperty);
}
public static void SetUnselectOnClickOutside(DependencyObject obj, bool value)
{
obj.SetValue(UnselectOnClickOutsideProperty, value);
}
public static readonly DependencyProperty UnselectOnClickOutsideProperty =
DependencyProperty.RegisterAttached("UnselectOnClickOutside", typeof(bool), typeof(DataGridBehaviors),
new UIPropertyMetadata(false, UnselectOnClickOutsidePropertyChangedCallback));
private static void UnselectOnClickOutsidePropertyChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs eventArgs)
{
DataGrid grid = depObj as DataGrid;
if (depObj != null)
{
if ((bool)eventArgs.OldValue == false && (bool)eventArgs.NewValue == true)
grid.MouseLeftButtonUp += AssociatedObjectUnselectOnClickOutside;
else if
((bool)eventArgs.OldValue == true && (bool)eventArgs.NewValue == false)
grid.MouseLeftButtonUp -= AssociatedObjectUnselectOnClickOutside;
}
}
private static void AssociatedObjectUnselectOnClickOutside(object sender, MouseButtonEventArgs e)
{
DataGrid datagrid = e.Source as DataGrid;
DependencyObject originalControl = e.OriginalSource as DependencyObject;
DataGridCell cell; DataGridRow row;
if (TryFindParent<DataGridRow>(originalControl, out row) && !TryFindParent<DataGridCell>(originalControl, out cell))
{
datagrid.UnselectAll();
}
}
private static bool TryFindParent<T>(DependencyObject child, out T result) where T : class
{
Contract.Requires(child != null, "child could not be null");
Contract.Ensures(Contract.Result<bool>() == true && Contract.ValueAtReturn<T>(out result) != null
&& result is T, "Result found rule violated");
Contract.Ensures(Contract.Result<bool>() == false && Contract.ValueAtReturn<T>(out result) == null,
"Result not found rule violated");
var parent = VisualTreeHelper.GetParent(child);
result = parent as T;
if (result != null)
return true;
else
if (parent != null)
return TryFindParent<T>(parent, out result);
else
return false;
}
}
Questo è un behavior applicabile ad ogni griglia tramite AttachedProperty:
<DataGrid ItemsSource="{Binding}" local:DataGridBehaviors.UnselectOnClickOutside="True" />
Et voilà! Les jeux sont faits
Tuesday, May 10, 2011
Come probabilmente saprete, WMAppManifest.xml è un file presente nei progetti per windows phone 7, Silverlight o XNA che siano.
Questo file DEVE essere editato prima di pubblicare l’applicativo; una delle sezioni da editare è la parte delle “Capabilities”, ovvero quello che l’applicazione sfrutta del telefono (sensori, networking, telefono, location ecc…).
Quali elementi usa la nostra bellissima app? basta seguire questo:
http://frisoderidder.wordpress.com/2011/02/08/using-phone-7-capability-detection-tool/
In pratica tra i componenti installati dall’sdk c’è anche un tool che, dato in pasto un xml di regole e la cartella di output, mostra quali elementi impostare.
Quello che forse non tutti sanno è che c’è anche questa libreria gratuita http://coding4fun.codeplex.com/ con la quale è possibile leggere facilmente le informazioni presenti nel file in questione (creando così facilmente una pagina “About”).
Basta referenziare la dll e utilizzare la classe PhoneHelper per esempio, per recuperare la versione, nel seguente modo:
PhoneHelper.GetAppAttribute("Version");
Saturday, April 23, 2011
Dato che sul forum ci sono state molte domande riguardo il binding di wpf con i TreeView, ecco una semplice guida step-by-step.
Per prima cosa, se occorre eseguire un binding gerarchico, serve una sorgente dati gerarchica. Ora me ne vengono in mente due:
- Xml: per sua natura l’xml ha una struttura gerarchica e ben si adatta al treeview
- Oggetti (leggasi composite pattern): in pratica classi “Composite” che hanno una proprietà IEnumerable<Component> Children.
Per il poco che mi viene in mente, tutto può essere ricondotto al secondo caso (il primo incluso ma non ce ne è bisogno, si salti pure avanti).
OGGETTI + COSTRUZIONE GERARCHIA DA FLATHIERARCHY:
supponiamo di avere una lista di oggetti (che può essere benissimo una DataTable o qualsiasi altra cosa
):
public class FlatHierarchyNode
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Text { get; set; }
}
Andremo a creare la nostra struttura nel seguente modo (ho lasciato da parte un po’ di “eleganza” e “pulizia” per comprimere il codice):
public class Composite
{
public FlatHierarchyNode Node { get; set; }
private List<FlatHierarchyNode> _allNodes;
public Composite(FlatHierarchyNode node, List<FlatHierarchyNode> allNodes)
{
this.Node = node;
this._allNodes = allNodes;
}
private IEnumerable<Composite> _children;
public IEnumerable<Composite> Children //nodi figlio del corrente nodo
{
get {
if(_children == null)
_children = Composite.getChildren(Node.Id, _allNodes);
return _children;
}
}
public static IEnumerable<Composite> getChildren(int? parentId, List<FlatHierarchyNode> allNodes)
{
return from node in allNodes //da tutti i nodi
where node.ParentId == parentId //prendo i nodi figli
select new Composite(node, allNodes); //ritornando un Composite
}
}
è un po’ lunghetto ma abbastanza semplice. La parte principale è il metodo getChildren.
Avendo una struttura del genere, potremmo benissimo fare:
treeView1.ItemsSource = Composite.getChildren(null, new List<FlatHierarchyNode>(… … …));
Come eseguire il binding ora? Easy:
<TreeView Name="treeView1" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Node.Text}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In pratica andiamo a sostinuire il template impostando un HierarchicalDataTemplate . Questo componente permette di ciclare sui figli ItemsSource="{Binding Path=Children}", riapplicando se stesso come datatemplate.
Del nostro oggetto andremo a mostrare come testo Text="{Binding Path=Node.Text}".
XML:
Detto che anche con Xml possiamo usare il codice qui sopra, tutto potrebbe risultare ancora più semplice senza neanche una riga di codice (detto che personalmente odio i datasource livello xaml):
<Window.Resources>
<XmlDataProvider x:Key="provider" Source="XMLFile1.xml" XPath="Sezioni/*" />
</Window.Resources>
Poi il TreeView è così definito:
<TreeView Name="treeView1" ItemsSource="{Binding Source={StaticResource ResourceKey=provider}}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding XPath=*}">
<TextBlock Text="{Binding XPath=@Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Supponendo di visualizzare tutti i nodi partendo da Sezioni/*, e mostrando, di tutti i nodi, l’attributo Name.
Cosa preferisco:
Beh… dipende molto dalle situazioni, però… decisamente il primo
Il secondo non mi piace proprio, semplicemente per il fatto che ho un XmlDataProvider lato UI che non mi piace. Si può recuperare da codebehind ma storcio il naso.
Inoltre l’XML può essere caricato con un XDocument/XElement e ricondotto molto facilmente al primo caso.
Post Scriptum XML:
Una cosa che non ho detto… I LOVE CONVERTERS! In wpf esistono questi magnifici oggetti e sposerei chi li ha inventati!
Per gli XML preferisco di gran lunga un approccio del genere:
XDocument doc = XDocument.Load("XMLFile1.xml");
this.treeView1.ItemsSource = doc.Elements();
poi dichiarare la treeview come segue:
<TreeView Height="227" Name="treeView1" Width="294">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=., Converter={StaticResource NodeToChildren}}">
<TextBlock Text="{Binding Path=., Converter={StaticResource NodeToTextConverter}}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
E creare i converters:
public class NodeToChildren : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
XElement element = value as XElement;
if (element != null)
return element.Elements();
return null;
}
public class NodeToTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
XElement element = value as XElement;
if (element != null)
return element.Attributes("Name").FirstOrDefault();
return null;
}
Insomma… ad ognuno il suo!
Sunday, April 03, 2011
Per chi, come me, non capisce il motivo delle divergenze (o meglio, di alcune divergenze) tra WPF e Silverlight, volevo segnalare Dr.Webb:
http://drwpf.com/blog/2010/05/05/value-coercion-for-the-masses/
Ottimo il video che esplica come configurare correttamente gli helper per consentire il CoerceValueCallback in Silverlight.
PS: come da titolo, dato che sto sviluppando su Windows Phone, aggiungo che la tecnica funziona anche su questo (magnifico) dispositivo.
Thursday, March 31, 2011
Voglio qui riassumere due concetti “base” (nel senso che non li approfondisco molto) riguardo il multithreading con wpf. In particolare due concetti:
- Eseguire componenti UI su thread diversi
- Aggiornare componenti di UI da thread diversi
Per far questo ho scritto una semplice finestra con il seguente contenuto:
<Button Content="Start another Window with same UI Thread"
Margin="5,5,5,2" Click="btnSameThread_Click" />
<Button Content="Start another Window with different UI Thread"
Margin="5,2,5,2" Click="btnNewThread_Click" />
<Button Content="Perform Long Operation"
Margin="5,10,5,10" Click="btnLongOperation_Click" />
<Button Content="Change Textbox of last window opened"
Margin="5,5,5,2" Click="btnChangeTextLastWindow_Click" />
<Button Content="Change Textbox of last window opened (secured)"
Margin="5,2,5,5" Click="btnChangeTextLastWindowSecured_Click" />
<TextBox Text="Try change this text" x:FieldModifier="public" x:Name="ChangeTextbox" />
Da notare l’ultima textbox con x:FieldModifier=”public”; in questo modo la textbox è accessibile anche dall’esterno della classe (in questo caso non serve, ma l’esempio deriva da un caso in cui il thread era su una classe esterna).
Ecco il codice:
MainWindow lastWindowOpened = null;
private void btnSameThread_Click(object sender, RoutedEventArgs e)
{
lastWindowOpened = new MainWindow(); //show window with same Thread
lastWindowOpened.Show();
}
private void btnNewThread_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread(new ThreadStart(() =>
{
lastWindowOpened = new MainWindow(); //the window is created in a different thread
lastWindowOpened.Show(); //show window
lastWindowOpened.Closed +=
(s, ec) => System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run(); //start message loop!
}));
t.SetApartmentState(ApartmentState.STA);//interoperability with com…
t.Start();
}
private void btnLongOperation_Click(object sender, RoutedEventArgs e) { Thread.Sleep(5000); }
private void btnChangeTextLastWindow_Click(object sender, RoutedEventArgs e)
{
//not safe for UI running on different thread
lastWindowOpened.ChangeTextbox.Text = "Cambio da altra window"; }
private void btnChangeTextLastWindowSecured_Click(object sender, RoutedEventArgs e)
{
lastWindowOpened.Dispatcher.BeginInvoke(new Action(() =>
{//if closed, if not null…
lastWindowOpened.ChangeTextbox.Text = "Cambio da altra window";
}));
}
Ora occorre capire due concetti:
- btnNewThread esegue un thread in cui mostra una nuova finestra. Il thread tuttavia non ascolta gli eventi della UI finché non si esegue Dispatcher.Run() (http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.run.aspx). Eseguendo il metodo statico, il thread corrente entra nel loop dei messaggi finché non si esegue InvokeShutdown(). Cosa succede se non si chiama InvokeShutdown()? La finestra viene chiusa, ma il thread continua ad ascoltare il loop dei messaggi per sempre. Quindi il thread non termina e l’applicazione resta appesa. TaskManager e killiamo
- btnChangeTextLastWindowSecured modifica il contenuto della textbox dell’ultima window aperta. In questo caso si “inietta” un delgate al thread che “possiede” l’elemento di UI. In qusto modo l’operazione viene eseguita dal thread affine alla UI senza creare problemi.
Ora il momento dei test:
- lanciamo la finestra, premiamo il primo bottone per lanciare una nuova window. Ora nella prima premiamo btnLongOperation e muoviamo le finestre…
Non si muovono! questo perchè le due finestre sono gestite dallo stesso thread che viene posto in sleep(). Corretto.
- lanciamo la finestra, premiamo il secondo bottone e poi btnLongOperation su una finestra e muoviamo l’altra…
Si muove! questo perchè le due finestre sono su due thread diversi e se si pone un thread in sleep mode, quella finestra è bloccata, ma l’altra no ed è libera di leggere i messaggi dal loop e quindi muovere la window.
- riprendiamo il secondo punto. Lanciamo due window su thread diversi e premiamo il 4° button. L’applicazione crasha. Questo perchè si accede ad un elemento di UI appartenente ad un altro thread. L’ultimo button è necessario.
- Ultimo punto, un po’ più complicato…
- Lanciamo una window
- Lanciamo la seconda window su un thread diverso con il secondo button
- sulla seconda window eseguiamo btnLongOperation. La seconda window è quindi bloccata e non si può muovere.
- Ora nella prima window premiamo l’ultimo button… cosa succede?
Niente. Il thread della prima window continua il loop dei messagge ed è libera. La seconda finestra è bloccata. Il testo non cambia fino a che il secondo thread non esce dallo sleep.
- Cosa succede se invece di BeginInvoke mettiamo Invoke? In questo caso anche il primo thread si blocca perchè l’invoke è sincrono e attende l’esecuzione dell’altro thread.
Liberi di eseguire altre prove (e postare esempi interessanti) 
Tuesday, March 22, 2011
Il mese scorso mi sono imbattuto in questo strano problema: La mia applicazione crashava su un particolare computer (che, per curiosità, aveva WinXP SP3 ma non credo sia questo il problema). L’utente sostiene che l’applicazione crasha anche stando a guardarla… molto strano.
0) Scoperta
L’eccezione non gestita, ricavata sia dal log applicativo (catturata nell’evento Application.DispatcherUnhandledException) sia da Event Viewer, era la seguente:
OpenClipboard Failed (Exception from HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN))
Dallo stack trace si evinceva che l’eccezione veniva lanciata dalla DataGrid di WPF4. Bene… andiamo in dettaglio.
1) Teoria della clipboard (fonte: Clipboard on MSDN Library)
Come è ovvio, la clipboard è un elemento di sistema a cui possono accedere più applicazioni contemporaneamente. A livello di API, ogni applicazione esegue, in genere, i seguenti passaggi:
- OpenClipboard()
- clear/read/set data
- CloseClipboard()
OpenClipboard() ritorna true se l’applicativo riesce a avere possesso della clipboard. In questo momento solamente lui è in grado di accedervi. Nessun’altra applicazione può accedervi (sia in scrittura che in lettura) prima che essa venga rilasciata con CloseClipboard().
2) Riproduzione del problema:
Create due applicazioni. La prima, un semplice Console Application, che esegue il seguente codice:
[DllImport("user32.dll")]
extern static bool OpenClipboard(IntPtr hWnd);
if (OpenClipboard(IntPtr.Zero))//lock clipboard
Thread.Sleep(60000);//così ho tempo per lanciare l’altra applicazione.
La seconda, WPF application, con un textbox e una DataGrid popolata e con elementi ClipboardContentBinding. Ora lanciate (non da VS) la console application, debuggate l’app WPF.
Premete CTRL+C sulla textbox. Blocco di pochi istanti ma niente. In questo caso il fatto che la clipboard sia lockata è correttamente gestita dalla textbox.
Premete CTRL+C selezionando qualcosa nella griglia… Bam! Ecco l’eccezione.
3) Soluzione:
Le strade sono due. La prima è scrivere una classe che deriva da DataGrid e, nell’override del metodo OnExecutedCopy() gestisce l’eccezione (credo si possa fare, anche se non ne sono troppo sicuro). Il secondo è gestire l’eccezione direttamente in Application.DispatcherUnhandledException. Poiché la DataGrid l’avevo usata in più punti nell’applicativo, non sapevo se questo era un caso isolato o se da altre parti sorgesse lo stesso problema e #soprattutto# dato i tempi stretti, ho optato per la seconda con questo codice
var comEx = e.Exception as System.Runtime.InteropServices.COMException;
if (comEx != null)
{
switch (comEx.ErrorCode)
{
case -2147221040: //OpenClipboard Failed (Exception from HRESULT: 0x800401D0 (CLIPBRD_E_CANT_OPEN))
try
{
//Try get info about the application which lock the clipboard
IntPtr handle = GetOpenClipboardWindow();
if (handle != IntPtr.Zero)
{
int capacity = GetWindowTextLength(new HandleRef(this, handle)) * 2;
StringBuilder stringBuilder = new StringBuilder(capacity);
GetWindowText(new HandleRef(this, handle), stringBuilder, stringBuilder.Capacity);
Trace.TraceWarning(string.Format("CLIPBOARD LOCKED BY {0}!", stringBuilder.ToString()));
}
}
finally
{
e.Handled = true; //Handle Exception. No error to user. No crash of the application.
}
return;
}
}
Trace.TraceError(e.Exception.Message);
Questo è tutto.
4) Riflessioni:
Chissà quale programma causava il problema… ve lo dirò quando mi farò dare il log
(se mai l’avrò)
5) Considerazioni:
Se qualcuno volesse pingare il team di sviluppo per spiegare il problema faccia pure, oppure se volesse spiegarmi come fare contattatemi.
Sunday, March 20, 2011
Ciao a tutti.
Essendo questo il mio primo post, inizierei con le presentazioni.
Mi chiamo Sarati Roberto, laureato in ingegneria informatica presso il Politecnico di Milano, attualmente lavoro in Avanade Italia; iniziando la mia esperienza con VBA, passando poi in Visual Basic 6, mi sono subito appassionato del mondo .NET fin dalla sua prima uscita. Arrivando dal mondo VB ho iniziato con VB.NET ma dopo qualche tempo ho deciso di passare al mondo C# in quanto lo ritenevo (e ritengo tuttora) un linguaggio con sintassi più ordinata e (soprattutto) meno prolissa.
Per quanto riguarda i miei interessi... beh, direi che potrei esprimere il mio stato con un frammento di codice:
Interest[] interests = Repository.PersonalInterests;
int freeSlot = FindFreeInterestSlotIndex(interests);
Assert.IsTrue(freeSlot > 0, "Ok, as usual, no free slot for new interest");
Detto questo, attualmente le mie attenzioni vanno a (non in ordine di tempo allocato/importanza): WP7, Orchard, NHibernate, ASP.NET MVC, Synch Framework, SF, OOXML, XNA, EF code first, Sql Server Denali, Farseer Engine... ovviamente non mancano WPF e Silverlight e molto altro che ora non ricordo. Il tutto per colpa merito di quella maledetta Microsoft che sforna una tecnologia al secondo... beh dai, non posso che ringraziarla, mostra nuove strade e per chi ha passione non può che far piacere.
Ovviamente in questo blog parlerò soprattutto di WPF, Silverlight e Window Phone (se mi è permesso, la tecnologia del resto è quella). Per tutto il resto mi sto organizzando nell'apertura di un altro blog; pensavo a wordpress, ma forse me ne gestirò uno io con orchard... vedrò di comunicarlo prossimamente.
Bene, arrivati a questo punto non posso far altro che augurarvi un caloroso benvenuto nel mio blog, spero di non essere banale e di essere soprattutto utile per qualche visitatore occasionale :)
A presto.