Il seguente esempio mostra come fare per inserire delle immagini in una listbox. La visualizzazione prevede gli items messi in orizzontale e per ogni item e' prevvista un'immagine e un testo alla base.
Per tale scopo viene creata una listbox facente riferimento ad una risorsa "listStyle" che definisce il template per l'item da visualizzare. Si noti che viene nascosta la barra verticale, mentre si utilizzarà solo quello orizzontale.
<ListBox
ItemContainerStyle="{StaticResource listStyle}"
ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
A questo punto nella sezione Resources della pagine e del controllo definiamo lo stile per l'item.
<Style TargetType="{x:Type ListBoxItem}" x:Key="listStyle">
<Setter Property="Template" Value="{StaticResource listControlTemplate}" ></Setter>
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=ActualHeight}" />
</Style>
In questo modo definiamo i SETTER e facciamo in modo che se la nostra listbox e' ridimensionabile in altezza, gli item verranno ridimensionati in modo da occupare tutta l'altezza e la larghezza verrà ridimensionata in modo proporzionale.
Definiamo quindi lo style listControlTemplate.
<ControlTemplate x:Key="listControlTemplate" TargetType="{x:Type ListBoxItem}" >
<Border x:Name="BorderImage" Margin="3,3,3,20" >
<Grid x:Name="listItemGrid" VerticalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Border BorderThickness="1" Background="Black" >
<Image x:Name="coverImage2" Tag="{Binding nPage}" Source="{Binding ., Converter={StaticResource ImageConverter} }" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Grid.Row="0" RenderTransformOrigin="1,1">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
</Border>
<TextBlock Grid.Row="1" Text="{Binding nPage}" FontWeight="Bold"
HorizontalAlignment="Center" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=sfondo}" Value="1">
<Setter Property="Background" Value="Aquamarine" TargetName="BorderImage"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding sfondo}" Value="10" >
<Setter Property="Background" Value="Black" TargetName="BorderImage"></Setter>
</DataTrigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Blue" TargetName="BorderImage"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Quindi definiamo che il binding dell'immagine avviene tramite ImageConvert.
<local:ImageConverter x:Key="ImageConverter" />
Questa e' una classe nella quale possiamo prendere le nostre immagini da file, da database o altro. Ovviamente l'informazione sarà nel campo (io normalmente uso delle classi in qui definisco i campi e poi per la lista dei dati uso una ObservableCollection che collego alla listbox.
La classe viene definita in questo modo :
#region "Converter Image"
public sealed class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
try
{
BitmapSource btImage = null;
PageInfo pg = (PageInfo)value;
if (pg.IconPath != "")
{
BitmapImage ba = new BitmapImage(new Uri(pg.IconPath));
ba.BeginInit();
ba.CacheOption = BitmapCacheOption.OnLoad;
ba.EndInit();
btImage = ba;
}
else
{
btImage =new BitmapImage();
}
if (btImage.CanFreeze) btImage.Freeze();
return btImage;
}
catch
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
#endregion
La classe PageInfo contiene i campi di cui faccio binding nella listbox; nel campo IconPath c'e' il path dell'immagine.
Ricordarsi di fare il Freeze dell'immagine altrimenti il GC non libererà mai la memoria anche nel caso si pulisca la listview.
Visualizzazione post con etichetta Codice. Mostra tutti i post
Visualizzazione post con etichetta Codice. Mostra tutti i post
lunedì 20 giugno 2011
martedì 3 maggio 2011
Selezionare il contenuto di una TextBox al Focus
Link originale: http://stackoverflow.com/questions/660554/how-to-automatically-select-all-text-on-focus-in-wpf-textbox.
La modifica consiste nell’includere il comportamento come un behavior. Ecco il codice:
E’ possibile ora aggiungere nello xaml il comportamento aggiungendo il behavior:
La modifica consiste nell’includere il comportamento come un behavior. Ecco il codice:
public static class TextBoxBehaviors { public static void SetSelectOnFocus(UIElement element, Boolean value) { element.SetValue(SelectOnFocusProperty, value); } public static bool GetSelectOnFocus(UIElement element) { return (bool)element.GetValue(SelectOnFocusProperty); } public static readonly DependencyProperty SelectOnFocusProperty = DependencyProperty.RegisterAttached("SelectOnFocus", typeof(bool), typeof(TextBoxBehaviors), new FrameworkPropertyMetadata(false, SelectOnFocusPropertyChangedCallback)); private static void SelectOnFocusPropertyChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs eventArgs) { TextBox tb = depObj as TextBox; if (tb != null) { if ((bool)eventArgs.OldValue == false && (bool)eventArgs.NewValue == true) { //attach events tb.PreviewMouseLeftButtonDown += SelectivelyIgnoreMouseButton; tb.GotKeyboardFocus += SelectAllText; tb.MouseDoubleClick += SelectAllText; } else if ((bool)eventArgs.OldValue == true && (bool)eventArgs.NewValue == false) { //detach events tb.PreviewMouseLeftButtonDown -= SelectivelyIgnoreMouseButton; tb.GotKeyboardFocus -= SelectAllText; tb.MouseDoubleClick -= SelectAllText; } } } private static void SelectivelyIgnoreMouseButton(object sender, MouseButtonEventArgs e) { DependencyObject parent = e.OriginalSource as UIElement; while (parent != null && !(parent is TextBox)) parent = VisualTreeHelper.GetParent(parent); if (parent != null) { var textBox = (TextBox)parent; if (!textBox.IsKeyboardFocusWithin) { textBox.Focus(); e.Handled = true; } } } private static void SelectAllText(object sender, RoutedEventArgs e) { var textBox = e.OriginalSource as TextBox; if (textBox != null) textBox.SelectAll(); } }
<TextBox Text="Selezionami per provare" behaviors:TextBoxBehaviors.SelectOnFocus="True" />
Etichette:
Codice,
Esempio,
TextBox,
Tips and Tricks
venerdì 15 aprile 2011
Parallelismo .Net
Con l'avvento del .NET Framework 3.5, arriva anche la tanto attesa preview pubblica delle Parallel Extensions for .NET Framework, una libreria managed per gestire il parallelismo di task e dati su hardware parallelo, con un work scheduler comune, liberando il programmatore dalla gestione dei dettagli inerenti la concorrenza. Oltre al download del package completo di binari ed esempi, è disponibile anche quello della sola documentazione.
La necessità del .NET Framework 3.5 è legata alla presenza di Parallel Language Integrated Query (PLINQ), il paradigma dichiarativo di codice parallelo che permette allo sviluppatore di esprimere cosa vuole ottenere piuttosto che come, direttamente derivato dalle caratteristiche uniche al mondo di LINQ. PLINQ viene esposto dal namespace System.Linq contenuto nell'assembly System.Threading.dll, che andrà referenziato in ogni progetto.
Alle funzionalità di PLINQ si accede in due modi. Il primo è tramite la classe System.Linq.ParallelEnumerable, che espone un'implementazione concorrente dei medesimi metodi esposti nel core da un'enumeration LINQ. Per fissare le idee, una query su una collection System.Linq.Enumerable viene effettuata, in LINQ, dal seguente codice:
La potenza espressiva di questa sintassi è già di per sé formidabile, e dà una pallida idea degli inarrivabili vertici cui è giunto il framework .NET. Ma la meraviglia delle meraviglie è la sintassi con cui la medesima query può essere eseguita in maniera concorrente:
Nessun trucco: semplicemente, si tratta di sostituire ogni chiamata alla classe System.Linq.Enumerable con una chiamata a System.Linq.ParallelEnumerable. In buona sostanza, chi ha sviluppato un'applicazione con LINQ, la renderà multiprocessore con un Trova e Sostituisci. I mesi di lavoro, lasciamoli pure agli sviluppatori non-.NET.
La seconda sintassi trae vantaggio dagli extensions methods di C# 3.0, e si applica in maniera naturale alla sintassi compatta delle query LINQ. La stessa query di cui sopra poteva essere espressa nella seguente forma:
alla quale corrisponde la naturale implementazione parallela:
Tutto quello che abbiamo fatto è stato invocare il metodo AsParallel sull'enumeration LINQ, nel suo overload più usato, quello senza parametri. Anche qui, trasformare la propria applicazione in una in grado di girare su sistemi multiprocessore diventa una semplice questione di Trova e Sostituisci. La bellezza del metodo AsParallel è che non si applica solo alle enumeration LINQ. Consideriamo la seguente funzione, che anagramma le lettere in un array di parole:
La sua versione parallela è semplicemente:
Anche qui, una semplice questione di Trova e Sostituisci. Naturalmente, come ben sa chi si occupa di calcolo parallelo, a differenza della programmazione ordinaria non è garantito che l'ordine degli output di elaborazione sia lo stesso degli input. PLINQ ha però una semplice soluzione Trova e Sostituisci anche per questo problema, e consiste in un banale overload di AsParallel, che accetta un valore ParallelQueryOptions:
L'unico limite di PLINQ, del resto comune a tutte le librerie di calcolo parallelo, sta nelle query che per via di effetti collaterali non sono parallelizzabili, dettagliatamente descritte nella sezione "Parallelism Blockers" della documentazione.
A dispetto della sua incredibile potenza espressiva, esistono degli scenari, legati alla presenza di codice legacy che fa uso del parallelismo imperativo di dati e task e che è necessario migrare, in cui PLINQ è una soluzione efficace, ma non ottima; per tali casi (dai quali è esclusa ogni applicazione già sviluppata facendo uso di LINQ) le Parallel Extensions for .NET Framework espongono la Task Parallel Library ed i suoi due namespace, System.Threading e System.Threading.Tasks, contenuti anch'essi nell'assembly System.Threading.dll.
La necessità del .NET Framework 3.5 è legata alla presenza di Parallel Language Integrated Query (PLINQ), il paradigma dichiarativo di codice parallelo che permette allo sviluppatore di esprimere cosa vuole ottenere piuttosto che come, direttamente derivato dalle caratteristiche uniche al mondo di LINQ. PLINQ viene esposto dal namespace System.Linq contenuto nell'assembly System.Threading.dll, che andrà referenziato in ogni progetto.
Alle funzionalità di PLINQ si accede in due modi. Il primo è tramite la classe System.Linq.ParallelEnumerable, che espone un'implementazione concorrente dei medesimi metodi esposti nel core da un'enumeration LINQ. Per fissare le idee, una query su una collection System.Linq.Enumerable viene effettuata, in LINQ, dal seguente codice:
<span style="font-size:1.0em">
IEnumerable data = ...;
var q = Enumerable.Select(Enumerable.OrderBy(Enumerable.Where(data, x => p(x)),
x => k(x)),
x => f(x));
foreach (var e in q) a(e);
</span>
<span style="font-size:1.0em">
IEnumerable data = ...;
var q = ParallelEnumerable.Select(ParallelEnumerable.OrderBy(ParallelEnumerable.Where(data, x => p(x)),
x => k(x)),
x => f(x));
foreach (var e in q) a(e);
</span>
La seconda sintassi trae vantaggio dagli extensions methods di C# 3.0, e si applica in maniera naturale alla sintassi compatta delle query LINQ. La stessa query di cui sopra poteva essere espressa nella seguente forma:
<span style="font-size:1.0em">
IEnumerable data = ...;
var q = data.Where(x => p(x)).Orderby(x => k(x)).Select(x => f(x));
foreach (var e in q) a(e);
</span>
<span style="font-size:1.0em">
IEnumerable data = ...;
var q = data.AsParallel().Where(x => p(x)).Orderby(x => k(x)).Select(x => f(x));
foreach (var e in q) a(e);
</span>
<span style="font-size:1.0em">
Public Shared Function Scramble( _ByVal words As String()) As String()
Return (From word In words _Select ScrambleWord(word)).ToArray()
End Function
</span>
<span style="font-size:1.0em">
Public Shared Function Scramble( _ByVal words As String()) As String()
Return (From word In words.AsParallel() _
Select ScrambleWord(word)).ToArray()
End Function
</span>
<span style="font-size:1.0em">
Public Shared Function Scramble( _ByVal words As String()) As String()
Return (From word In words.AsParallel( _
<b>ParallelQueryOptions.PreserveOrdering</b>)_Select ScrambleWord(word)).ToArray()
End Function
</span>
A dispetto della sua incredibile potenza espressiva, esistono degli scenari, legati alla presenza di codice legacy che fa uso del parallelismo imperativo di dati e task e che è necessario migrare, in cui PLINQ è una soluzione efficace, ma non ottima; per tali casi (dai quali è esclusa ogni applicazione già sviluppata facendo uso di LINQ) le Parallel Extensions for .NET Framework espongono la Task Parallel Library ed i suoi due namespace, System.Threading e System.Threading.Tasks, contenuti anch'essi nell'assembly System.Threading.dll.
martedì 15 marzo 2011
ListView Riordinabile
Una delle esigenze più frequenti durante lo sviluppo di applicazioni con
Windows Presentation Foundation è la rappresentazione di dati nel classico
formato Grid. In Windows Presentation Foundation, almeno fino alla versione 3.5
del framework, viene
utilizzato il controllo ListView.
Il controllo ListView di WPF, infatti, mette a disposizione un tipo di visualizzazione di tipo, appunto, GridView che permette la gestione di più colonne. Nell’esempio seguente viene definito un controllo ListView con modalità di visualizzazione GridView composta da 4 colonne in binding con alcune proprietà dell’entità Person.
Nel listato, come noterete, viene definito l’handler dell’evento Click di GridViewColumnHeader dove verrà gestito, appunto, il riordinamento della colonna cliccata.
Per ottenere l’ordinamento della collection utilizzeremo un metodo chiamato Sort che, attraverso l’uso di una CollectionViewSource effettua l’ordinamento per il campo passato come parametro al metodo (sortBy).
Passiamo ora alla gestione dell’evento di click sull’header di una colonna della ListView.
Come potete vedere, la logica presente nell’evento è abbastanza semplice. Qui, infatti, viene, innanzitutto, intercettato l’intestazione cliccata, e, in base alla direzione di ordinamento (Ascending o Descending) viene applicato un template diverso per visualizzare una freccetta nell’intestazione per identificarnet, appunto, l’orientamento.
Ovviemente, in questo evento, viene richiamata il metodo Sort dichiarato in precedenza. Il codice XAML per la definizione dei template da utilizzare per la visualizzazione delle freccetta di indicazione nelle intestazioni è il seguente.
Concludendo, quindi, ad un click su qualsiasi intestazione di colonna, viene effettuato l’ordinamento per la colonna in oggetto e sostituito il template dell’intestazione stessa affinchè venga in essa visualizzata una freccetta per identificarne la direzione dell’ordinamento effettuato.
Il controllo ListView di WPF, infatti, mette a disposizione un tipo di visualizzazione di tipo, appunto, GridView che permette la gestione di più colonne. Nell’esempio seguente viene definito un controllo ListView con modalità di visualizzazione GridView composta da 4 colonne in binding con alcune proprietà dell’entità Person.
<ListView x:Name="listPersons" ItemsSource="{StaticResource persons}" BorderThickness="0" Margin="5" GridViewColumnHeader.Click="GridViewColumnHeader_Click" > <ListView.View> <GridView AllowsColumnReorder="True"> <GridViewColumn Header="ID" DisplayMemberBinding="{Binding Path=ID}" Width="50" /> <GridViewColumn Header="FirstName" DisplayMemberBinding="{Binding Path=FirstName}" Width="100"/> <GridViewColumn Header="LastName" DisplayMemberBinding="{Binding Path=LastName}" Width="100"/> <GridViewColumn Header="City" DisplayMemberBinding="{Binding Path=City}" Width="80" /> </GridView> </ListView.View> </ListView>
Nel listato, come noterete, viene definito l’handler dell’evento Click di GridViewColumnHeader dove verrà gestito, appunto, il riordinamento della colonna cliccata.
Per ottenere l’ordinamento della collection utilizzeremo un metodo chiamato Sort che, attraverso l’uso di una CollectionViewSource effettua l’ordinamento per il campo passato come parametro al metodo (sortBy).
private void Sort(string sortBy, ListSortDirection direction) { ICollectionView dataView = CollectionViewSource.GetDefaultView(listPersons.ItemsSource); dataView.SortDescriptions.Clear(); SortDescription sd = new SortDescription(sortBy, direction); dataView.SortDescriptions.Add(sd); dataView.Refresh(); }
Passiamo ora alla gestione dell’evento di click sull’header di una colonna della ListView.
GridViewColumnHeader _lastSelectedHeader = null; ListSortDirection _lastDirection = ListSortDirection.Ascending; void GridViewColumnHeader_Click(object sender, RoutedEventArgs e) { GridViewColumnHeader selectedHeader = e.OriginalSource as GridViewColumnHeader; ListSortDirection direction; if (selectedHeader != null) { if (selectedHeader.Role != GridViewColumnHeaderRole.Padding) { if (selectedHeader != _lastSelectedHeader) { direction = ListSortDirection.Ascending; } else { if (_lastDirection == ListSortDirection.Ascending) { direction = ListSortDirection.Descending; } else { direction = ListSortDirection.Ascending; } } string header = string.Empty; if (selectedHeader.Column.CellTemplateSelector != null) { header = selectedHeader.Column.Header.ToString(); } else if (selectedHeader.Column.DisplayMemberBinding != null) { header = ((Binding)selectedHeader.Column.DisplayMemberBinding).Path.Path; } else { return; } Sort(header, direction); if (direction == ListSortDirection.Ascending) { selectedHeader.Column.HeaderTemplate = FindResource("HeaderTemplateArrowUp") as DataTemplate; } else { selectedHeader.Column.HeaderTemplate = FindResource("HeaderTemplateArrowDown") as DataTemplate; } // Remove arrow from previously sorted header if (_lastSelectedHeader != null && _lastSelectedHeader != selectedHeader) { _lastSelectedHeader.Column.HeaderTemplate = null; } _lastSelectedHeader = selectedHeader; _lastDirection = direction; } } }
Come potete vedere, la logica presente nell’evento è abbastanza semplice. Qui, infatti, viene, innanzitutto, intercettato l’intestazione cliccata, e, in base alla direzione di ordinamento (Ascending o Descending) viene applicato un template diverso per visualizzare una freccetta nell’intestazione per identificarnet, appunto, l’orientamento.
Ovviemente, in questo evento, viene richiamata il metodo Sort dichiarato in precedenza. Il codice XAML per la definizione dei template da utilizzare per la visualizzazione delle freccetta di indicazione nelle intestazioni è il seguente.
<DataTemplate x:Key="HeaderTemplateArrowUp"> <DockPanel> <TextBlock HorizontalAlignment="Center" Text="{Binding}"/> <Path x:Name="arrow" StrokeThickness = "1" Fill="Black" Data="M 5,10 L 15,10 L 10,5 L 5,10"/> </DockPanel> </DataTemplate> <DataTemplate x:Key="HeaderTemplateArrowDown"> <DockPanel> <TextBlock HorizontalAlignment="Center" Text="{Binding }"/> <Path x:Name="arrow" StrokeThickness="1" Fill="Black" Data= "M 5,5 L 10,10 L 15,5 L 5,5"/> </DockPanel> </DataTemplate>
Concludendo, quindi, ad un click su qualsiasi intestazione di colonna, viene effettuato l’ordinamento per la colonna in oggetto e sostituito il template dell’intestazione stessa affinchè venga in essa visualizzata una freccetta per identificarne la direzione dell’ordinamento effettuato.
lunedì 10 gennaio 2011
Dispatcher
Il Dispatcher è un oggetto presente sia in WPF che in Silverlight ed è responsabile della coda di operazioni che si susseguono durate il ciclo di vita di un'operazione. Il comportamento è simile alla message pump di Win32: un ciclo continuo attende che una nuova DispatcherOperation venga accodata e la esegue appena possibile. L'unica differenza risiede nella possibilità di dare una priorità nell'operazione così da garantire per esempio che le operazioni di rendering abbiano la precedenza su quelle di input o su quelle di background.
In WPF attraverso la proprietà Hooks del Dispatcher si hanno a disposizione un po' di eventi per monitorare (occhio che si strozza parecchio il processamento delle richieste) l'accondamento delle operazioni. Per prima cosa si può notare quante operazioni vengono eseguite senza che ce ne accorgiamo. Il thread infatti è uno solo e la fluidità che si può percepire nell'interazione con i controlli o nelle animazioni è solo data dal fatto che la macchina processa le operazioni velocemente, ma ecco perché non appena si esegue qualcosa di più dispendioso per la CPU o si resta in attesa di una risposta, si causa il blocco dell'intera applicazione.
Nel thread del Dispatcher infatti ci finiscono tutti gli input da tastiera, mouse e altri device (il touch in futuro), arrivano i messaggi dalle altre finestre Win32, vengono gestiti gli handler degli eventi dei controlli e quindi eseguito il proprio codice, viene effettuato il rendering di tutti gli elementi presenti nell'applicazione e vengono inoltre gestite tutte le animazioni. Un semplice oggetto con Storyboard associata che varia per esempio nel tempo la larghezza dell'elemento, fa sì che nel dispatcher vengano accodate tantissime operazioni, fino anche 50/60 al secondo, pari a quanti frame al secondo si intende renderizzare. E' interessante poi notare attraverso l'evento OperationAborted come, nel caso la CPU non è in grado di soddisfare tale rate o per rallentamenti dovuti ad operazioni intermedie fra un frame e l'altro, il motore abortistica alcuni di questi frame, sacrificando la fluidità, ma mantenendo l'obiettivo: raggiungere un valore in un intervallo ben preciso. Questa è una delle principali differenze che Silverlight ha con Flash dove quest'ultimo lavora ad un frame rate fisso preindicato, ignorando il carico di lavoro che la macchina ha.
In WPF inoltre si ha a disposizione il signor DispatcherFrame che permette di aprire una finestra sul medesimo thread, bloccando però il creatore della finestra. Questa tecnica viene utilizzata per le finestre modali dove chiamando ShowDialog il chiamante del metodo attende che la finestra venga chiusa, mentre tutte le operazioni accodate nel Dispatcher vengano comunque processate al suo interno, mantenendo attive le animazioni della finestra chiamante e della finestra modale, e reagendo agli input dell'utente. Il fatto che sulla finestra chiamante non si possa interagire è solo un impedimento di Windows, ma tecnicamente è possibile accodare operazioni che intervengano su di essa.
Il DispatcherFrame può essere usato anche per un uso alternativo. Per capirci su un click di pulsante si può fare:
Se eseguito, nonostate il timer scatti dopo tre secondi dando tutto il tempo di processare l'istruzione "Frame uscito", l'output è:
Creazione DispatcherFrame
Frame in termine
Frame uscito
In WPF attraverso la proprietà Hooks del Dispatcher si hanno a disposizione un po' di eventi per monitorare (occhio che si strozza parecchio il processamento delle richieste) l'accondamento delle operazioni. Per prima cosa si può notare quante operazioni vengono eseguite senza che ce ne accorgiamo. Il thread infatti è uno solo e la fluidità che si può percepire nell'interazione con i controlli o nelle animazioni è solo data dal fatto che la macchina processa le operazioni velocemente, ma ecco perché non appena si esegue qualcosa di più dispendioso per la CPU o si resta in attesa di una risposta, si causa il blocco dell'intera applicazione.
Nel thread del Dispatcher infatti ci finiscono tutti gli input da tastiera, mouse e altri device (il touch in futuro), arrivano i messaggi dalle altre finestre Win32, vengono gestiti gli handler degli eventi dei controlli e quindi eseguito il proprio codice, viene effettuato il rendering di tutti gli elementi presenti nell'applicazione e vengono inoltre gestite tutte le animazioni. Un semplice oggetto con Storyboard associata che varia per esempio nel tempo la larghezza dell'elemento, fa sì che nel dispatcher vengano accodate tantissime operazioni, fino anche 50/60 al secondo, pari a quanti frame al secondo si intende renderizzare. E' interessante poi notare attraverso l'evento OperationAborted come, nel caso la CPU non è in grado di soddisfare tale rate o per rallentamenti dovuti ad operazioni intermedie fra un frame e l'altro, il motore abortistica alcuni di questi frame, sacrificando la fluidità, ma mantenendo l'obiettivo: raggiungere un valore in un intervallo ben preciso. Questa è una delle principali differenze che Silverlight ha con Flash dove quest'ultimo lavora ad un frame rate fisso preindicato, ignorando il carico di lavoro che la macchina ha.
In WPF inoltre si ha a disposizione il signor DispatcherFrame che permette di aprire una finestra sul medesimo thread, bloccando però il creatore della finestra. Questa tecnica viene utilizzata per le finestre modali dove chiamando ShowDialog il chiamante del metodo attende che la finestra venga chiusa, mentre tutte le operazioni accodate nel Dispatcher vengano comunque processate al suo interno, mantenendo attive le animazioni della finestra chiamante e della finestra modale, e reagendo agli input dell'utente. Il fatto che sulla finestra chiamante non si possa interagire è solo un impedimento di Windows, ma tecnicamente è possibile accodare operazioni che intervengano su di essa.
Il DispatcherFrame può essere usato anche per un uso alternativo. Per capirci su un click di pulsante si può fare:
DispatcherFrame frame = new DispatcherFrame(true); Debug.WriteLine("Creazione DispatcherFrame"); // Timer che scatta dopo 3 secondi System.Timers.Timer timer = new System.Timers.Timer(3000); timer.AutoReset = false; timer.Elapsed += delegate { Debug.WriteLine("Frame in termine"); frame.Continue = false; }; timer.Start(); // Avvio il frame e attendo che finisca Dispatcher.PushFrame(frame); Debug.WriteLine("Frame uscito");
Creazione DispatcherFrame
Frame in termine
Frame uscito
Iscriviti a:
Post (Atom)