Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

Παρουσίαση με Ετικέτες

Όλες οι Ετικέτε... » silverlight   (RSS)

An Extensible WPF client layout with RibbonTab submenus using MEF

I was just experimenting with extensibility using MEF (MEF is a .NET resident for .NET4 and SL4), when I created a small proof of this functionality that I could share via my blog. It is a semi-patterned (only for the main shell, my shell, not a PRISM one with viewregions and stuff, just an MVVM main area implemented with MVVMLight), but it does what it is supposed to do. So what is is supposed to do? It is an application, which can be extended by simply, drag and dropping, dlls in the Plugins directory. Every plugin, contains a certain amount, of RibbonTabs and each one, can provide applications, via RibbonTabItems (utilized the Microsoft Ribbon for WPF October 2010). By clicking each one of these items, one can open the corresponding application in the closable TabControl next to the plugin option menu. This menu, by the way is a WPF Outlook bar reused by this codeproject. The Favorites menu is a subset of the Applications option. The plugin system is based on MEF, demonstrating a simple plugin system.

Simple View

image

Extended View

image

So, this application consists of a simple ViewModel which contains all the necessary properties and a simple View, databound to this ViewModel. The most important Bindings are the one of the SelectedPlugin.RibbonTabCollection and the OpenedApplications of the TabControl. Upon selection of a certain tabitem, we switch to the corresponding plugin too (containing the application of the selected item).  Applications are some usercontrols (with codebehind), I constructed randomly and exposed as *concrete* implementations via the plugin mechanism. So, how does this work?

Managed Extensibility Framework

I use two main interfaces, one for Applications…

[code]
public interface IApplication
{
    String ApplicationName { get; set; }
    ContentControl ApplicationView { get; set; }
}[/code]

…and one for plugins.  The interface IPlugin, is marked as InheritedExport (a MEF attribute), mentioning that whoever implements this, can be discovered by MEF and used at some way (discribed below)!

[code][InheritedExport]
public interface IPlugin
{
    ObservableCollection<RibbonTab> RibbonTabCollection { get; }
    ObservableCollection<IApplication> ApplicationCollection { get; }
}

public interface IPluginMetadata
{
    string Name { get; }
    string Version { get; }
}

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : ExportAttribute
{
    public PluginMetadataAttribute(string name, string version)
        : base(typeof(IPlugin))
    {
        Name = name;
        Version = version;
    }

    public string Name { get; set; }
    public string Version { get; set; }
}[/code]

So our plugin has a RibbonTabcollection (again constructing via XAML), and an ApplicationCollection. It also has metadata like the name. This string, is the one, databound in the outlookbar list, providing for the name of the selection via a data template.

[code]<DataTemplate x:Key="PluginItemTemplate">
    <TextBlock Text="{Binding Metadata.Name}" />
</DataTemplate>[/code]

Messaging between views is implemented via a simple Messaging schema, using the provided mechanism of MVVMLight.

The important thing is the AllPlugins property on the ViewModel. It is declared as an observable collection of lazy items, that are constructed upon dereferencing the “Value” property of each Lazy (of IPlugin) instance. How is this dereference achieved? It is achieved for free, by the data binding mechanism. Notice the SelectedValue and SelectedValuePath, pair Smile.

[code]<ListBox ItemsSource="{Binding AllPlugins}"
                         SelectionMode="Single"
                         ItemTemplate="{StaticResource PluginItemTemplate}"
                         SelectedValue="{Binding SelectedPlugin, Mode=TwoWay}"
                         SelectedValuePath="Value" />[/code]

Notice the ImportMany attribute enabling the collection to be populated by the MEF mechanism.

[code][ImportMany]
private ObservableCollection<Lazy<IPlugin, IPluginMetadata>> allPlugins
    = new ObservableCollection<Lazy<IPlugin, IPluginMetadata>>();
public ObservableCollection<Lazy<IPlugin, IPluginMetadata>> AllPlugins
{
    get
    {
        return allPlugins;
    }
    set
    {
        allPlugins = value;
        RaisePropertyChanged("AllPlugins");
    }
}[/code]

Final major question! How are plugins discovered? I have marked a directory on my system as Plugins (via App.config – change that when you run the demo). The catalog, that is going to discover all these types (the ones that are dropped there) is a DirectoryCatalog which can compose all the necessary types.

[code]DirectoryCatalog directoryCatalog =

new DirectoryCatalog(ConfigurationManager.AppSettings["PluginsDirectory"]);

container = new CompositionContainer(directoryCatalog);

//Fill the imports of this object
try
{
    this.container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
    Debug.WriteLine(compositionException.ToString());
}[/code]

At the demo provided you will see that there are two projects, referencing the contract and implementing as needed.

image

This is only, one simple use of MEF. Future work on this demo, could be to provide dynamic discovery of ViewModel and View types, leaving to MEF the instantiation of them, leaving MEF the inversion of control responsibility too!

I hope you enjoyed this first little tutorial for MEF. The same principles can be applied to Silverlight application for discovering XAP’s for example.

Download the demo

Posted: Τετάρτη, 1 Δεκεμβρίου 2010 1:50 πμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , , ,

IT Pro | Dev Connections 2010 - "Και Line-of-Business και Rich Internet Applications: The silver side of the moon"

Ευχαριστούμε όσους μας τίμησαν με την παρουσία τους στο IT Pro | Dev Connections 2010, στην παρουσίαση μας (Άγγελος Μπιμπούδης & Μάνος Κελαϊδίτης) με τίτλο "Και Line-of-Business και Rich Internet Applications: The silver side of the moon". Είδαμε πρακτικές και τεχνολογίες για RIA εφαρμογές σε Silverlight, πώς ξέραμε τις επιχειρησιακές εφαρμογές με Silverlight μέχρι τώρα και τι εφόδια έχουμε πια, για να φτιάχνουμε γρηγορότερα και πιο δομημένα web applications, συνδυάζοντας RIA και LoB χαρακτηριστικά.

Προσπαθήσαμε να πούμε όσα περισσότερα πράγματα γίνεται, δίνοντας ένα κβάντο πληροφορίας που μπορεί να χρησιμοποιηθεί άμεσα, ξεκινώντας με WCF Ria Services, MVVM design pattern και MEF για extensibility. Ο χρόνος ήταν περιορισμένος, αλλά πιστεύουμε ότι τα καταφέραμε (φάνηκε και από την αξιολόγηση :D).

Για όσους όμως, πιστεύουν ότι 75 λεπτά δεν έφτασαν (και αλήθεια θα είναι), σχεδιάζουμε να κάνουμε live meeting στο άμεσο μέλλον, οπότε stay tuned.

*Για να τρέξετε τα demos, θα χρειαστείτε: WCF Ria Services, Silverlight Toolkit, Async CTP, RX Extentions, MVVMLight

*Credits to: Guy Smith-Ferrier for the localization reference and material included. He has done a great work on internationalizing silverlight and localization in general. For more check.

ΠαρουσίασηΥλικό-Demos

image

Posted: Τρίτη, 30 Νοεμβρίου 2010 11:42 μμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , , ,

CloseTabBehavior για TabItems σε Silverlight TabControl

Χρησιμοποιώντας το tabcontrol του Silverlight, είδα ότι δεν έχει close button σε κάθε tab. Μερικές λύσεις που είδα από το net, έκαναν extend ένα tabItem, ή έβαζαν handler κάπου για να το υλοποιήσουν. Έτσι έφτιαξα ένα behavior το οποίο γίνεται attach, λίγο πιο κομψά. Στο OnClick, υλοποίησα ένα μικρό chunk λογικής για να διατηρείται το state.

To itemtemplate

<TabControl.ItemTemplate>     <DataTemplate>         <DockPanel Width="120">             <Button Content="X"                                                     Cursor="Hand"                     DockPanel.Dock="Right"                     Focusable="False"                     FontFamily="Courier"                     FontSize="9"                     FontWeight="Bold"                      Margin="0,1,0,0"                     Padding="0"                     VerticalContentAlignment="Bottom"                     Width="16" Height="16" >                 <interactivity:Interaction.Behaviors>                     <behavior:CloseTabBehavior/>                 </interactivity:Interaction.Behaviors>             </Button>             <ContentPresenter Content="{Binding ApplicationName}"                     VerticalAlignment="Center" />         </DockPanel>     </DataTemplate> </TabControl.ItemTemplate>

To behavior

public class CloseTabBehavior : Behavior<Button> {     protected override void OnAttached()     {         AssociatedObject.Click += OnClick;     }     protected override void OnDetaching()     {         AssociatedObject.Click -= OnClick;     }     private static void OnClick(object sender, RoutedEventArgs e)     {         TabItem tabItem = (sender as Button).GetParent<TabItem>();         object selectedItem = tabItem.DataContext;         TabControl tabControl = (sender as Button).GetParent<TabControl>();         IList list = (IList) tabControl.ItemsSource;         int index = list.IndexOf(selectedItem);         if (list.Count == 1)             tabControl.SelectedItem = null;         else if (index < list.Count - 1 )             tabControl.SelectedItem = list[++index];         else if (index == list.Count - 1)             tabControl.SelectedItem = list[--index];         try         {             list.Remove(selectedItem);         }         catch (Exception ex)         {             Debug.WriteLine(ex.Message);         }     } }

Όποιος έχει κάτι να προσθέσει, feel free!

 


Posted: Δευτέρα, 29 Νοεμβρίου 2010 2:39 μμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: ,

Ζωντανό, παγκόσμιο event για το μέλλον και για τα best practices του Silverlight

clip_image002

Θα ήθελα να ενημερώσω ότι υπάρχει ένα πολύ δυνατό παγκόσμιο event  για silverlight στις 2 Δεκεμβρίου, 2010 το οποίο θα έχει live video steaming.

Θα μιλήσει ο Scott Guthrie και άλλος κόσμος κατευθείαν από τα κεντρικά.

Η Εγγραφή είναι free.

Περισσότερες πληροφορίες, μπορείτε να βρείτε στο επίσημο site

Posted: Παρασκευή, 12 Νοεμβρίου 2010 4:16 μμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία:

Silverlight Αttached Βehavior: Φτιάχνοντας ένα Marquee TextBox Control

Έχω ένα Grid στο Silverlight Application και θέλω να κάνω ένα marquee textbox (autoscroll) από δεξιά προς τα αριστερά, μέσα στο κελί του Grid. Τι χρειάζεται να κάνω για να προσθέσω σε ένα control αυτή τη λειτουργία;

Για αρχή έχουμε ένα textbox και στα γρήγορα κάνουμε ένα Translate RenderTransform αλλάζοντας την τιμή, στον άξονα των x από θετικές σε αρνητικές τιμές.

<TextBlock Grid.Row="0" Grid.Column="0" Foreground="#FF2755AF"            FontSize="13.333" x:Name="ScrollText" Text="Really Really Really Really Really Really Large Text" >     <TextBlock.RenderTransform>         <TranslateTransform x:Name="translate" />     </TextBlock.RenderTransform>     <TextBlock.Triggers>         <EventTrigger RoutedEvent="Grid.Loaded">             <BeginStoryboard>                 <Storyboard RepeatBehavior="Forever">                     <DoubleAnimation                         From="400" To="-400"                         Storyboard.TargetName="translate"                         Storyboard.TargetProperty="X"                         Duration="0:0:20" />                 </Storyboard>             </BeginStoryboard>         </EventTrigger>     </TextBlock.Triggers> </TextBlock>

Δυστυχώς όμως έχουμε πολλά προβλήματα όπως φαίνεται. Το κείμενο εκτός του ότι φεύγει τελείως εκτός των bounds του Grid, γιατί έγινε βίαιο translate χωρίς φόβο και πάθος, είναι και clipped ως προς το content. Αν παρατηρείτε μάλιστα, είναι clipped στο μέγεθος του κελιού (0,0). Αυτό γίνεται γιατί το Grid, πριν απεικονίσει τα αντικείμενα που έχει στο visual tree, τα κάνει clip, αυτόματα.

image

Το πρώτο πρόβλημα μπορούμε να το χειριστούμε με clipping στο Grid. Το δευτερο όμως όχι.

<Grid.Clip>     <RectangleGeometry Rect="0,0,100,20"/> </Grid.Clip>

Οπότε θέλουμε να κάνουμε override το clipping του Grid, όπως και τo clipping του textbox. Ο Canvas μας βοηθάει και στις δύο περιπτώσεις, γιατί αφενός αφήνει ελεύθερο το Actual Size του textbox και αφετέρου έχει και αυτό Clipping dependency property ως UIElement.

<Canvas >     <Canvas.Clip>         <RectangleGeometry Rect="0,0,100,20"/>     </Canvas.Clip>     <TextBlock Grid.Row="0" Grid.Column="0" Foreground="#FF2755AF"                 FontSize="13.333" x:Name="ScrollText" Text="Really Really Really Really Really Really Large Text" >     <TextBlock.RenderTransform>         <TranslateTransform x:Name="translate" />     </TextBlock.RenderTransform>     <TextBlock.Triggers>         <EventTrigger RoutedEvent="Grid.Loaded">             <BeginStoryboard>                 <Storyboard RepeatBehavior="Forever">                     <DoubleAnimation                         From="300" To="-300"                         Storyboard.TargetName="translate"                         Storyboard.TargetProperty="X"                         Duration="0:0:20" />                 </Storyboard>             </BeginStoryboard>         </EventTrigger>     </TextBlock.Triggers>     </TextBlock> </Canvas>

Δυστυχώς όμως έχουμε και εδώ μερικά προβλήματα. Καρφωτές τιμές στο Rect και στο From / To του δεν μπορούν να συνδυαστούν με ένα δυναμικό μέγεθος του Text, οπότε για να γίνει το control λίγο reusable χρειάζεται λίγο refactoring.

Η βασική ιδέα είναι να υπολογίζουμε δυναμικά κάποια πράγματα, για να μην χρειάζεται να βάλουμε καρφωτές τιμές. Μπορούμε να το κάνουμε αυτό με ένα πολύ απλό pattern που λέγεται attached behavior. Για να μπορούμε από ένα κεντρικό σημείο να αλλάξουμε ιδιότητες του TextBlock χωρίς να το κάνουμε extend, χρησιμοποιούμε attached property. Ορίζουμε λοιπόν ένα attached property κάπου (στη main page σε εμάς, σε ένα behavior σε άλλες περιπτώσεις), δεν έχει σημασία, για το demo μας, το οποίο θα το δηλώσουμε στο textblock που μας ενδιαφέρει. Θυμίζουμε ότι attached properties είναι properties που ορίζονται σε κάποια κλάση και μπορούν να χρησιμοποιηθούν από άλλες. Τυπικό παράδειγμα το Grid.Row και Grid.Column που βάζουμε στα elements, που περιέχονται σε Grid Panel.

#region MakeScrollable Property public static void SetMakeScrollable(UIElement element, bool value) {     element.SetValue(MakeScrollableProperty, value); } public static bool GetMakeScrollable(UIElement element) {     return (Boolean)element.GetValue(MakeScrollableProperty); } public static readonly DependencyProperty MakeScrollableProperty =     DependencyProperty.RegisterAttached(         "MakeScrollable", typeof (bool), typeof (MainPage),         new PropertyMetadata(false, OnMakeScrollableChanged)); private static void OnMakeScrollableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {     FrameworkElement me = d as FrameworkElement;     me.Loaded += me_Loaded; } static void me_Loaded(object sender, RoutedEventArgs e) {     Clip(sender); } private static void Clip(object sender) {     FrameworkElement me = (FrameworkElement)sender; //Textblock size changed     FrameworkElement parent = (FrameworkElement)me.Parent;     if (GetMakeScrollable(me))     {         parent.Dispatcher.BeginInvoke(()=>         {             parent.Clip = new RectangleGeometry { Rect = new Rect(0, 0, parent.ActualWidth, parent.ActualHeight) };             var translateTransform = new TranslateTransform();             translateTransform.SetValue(NameProperty, "translate");             me.RenderTransform = translateTransform;             Storyboard storyboard = new Storyboard() { RepeatBehavior = RepeatBehavior.Forever};             DoubleAnimation doubleAnimation = new DoubleAnimation()             {                 From = parent.ActualWidth, To = -(me.ActualWidth), Duration = new Duration(new TimeSpan(0, 0, 20))             };             doubleAnimation.SetValue(Storyboard.TargetNameProperty, "translate");             doubleAnimation.SetValue(Storyboard.TargetPropertyProperty,  new PropertyPath("(TranslateTransform.X)"));             storyboard.Children.Add(doubleAnimation);             me.Resources.Add("StoryBoard", storyboard);             storyboard.Begin();         });     } } #endregion

Στη δική μας περίπτωση, ορίζουμε το MakeScrollableProperty. Όταν τεθεί από το textblock, αυτό καλεί την αντίστοιχα ορισμένη OnMakeScrollableChanged που στην ουσία κάνει το clipping.

<Canvas Grid.Row="0" Grid.Column="0" >     <TextBlock SilverlightApplication2:MainPage.MakeScrollable="true"                Text="Really Really Really Really Really Really Large Text "/> </Canvas>

image

Σίγουρα μπορούμε να κάνουμε ακόμα πιο κομψό τo “behavior”, διαχειριζόμενοι και τον canva, αλλά νομίζω ότι ήδη είναι αρκετά reusable για να χρησιμοποιηθεί στα πλαίσια ενός μικρού project. Τέλος, κρατήστε τη βασική ιδέα πίσω από το attached behavior. Αποκτούμε reference στο control που μας ενδιαφέρει μέσα από attached property, κάνοντας hooks και οτιδήποτε, μέσα από PropertyMetadata, αφήνοντας την προσθήκη λειτουργικότητας να γίνει δηλωτικά.

Smile

Posted: Τετάρτη, 20 Οκτωβρίου 2010 4:23 πμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , ,

A note to self: Silverlight DataBinding awesomeness μέσα από Expression Blend, XAML και κώδικα

 Post-it notes for la patilla

 

Το πιο δυνατό στοιχείο του Silverlight είναι η εκφραστικότητα των data binding μηχανισμών του με όλες τις μορφές που μας το παρέχει. Separation of concerns, animations, visual states, MVVM, αλληλένδετα controls και πολλά ακόμα “βαριά” ή ελαφρά features οφείλουν την ύπαρξή τους στο Data Binding.

Τι είναι Data Binding;

Data Binding είναι η σύνδεση μεταξύ μίας πηγής δεδομένων και ενός προορισμού.

Στο silverlight η πηγή είναι ένα CLR αντικείμενο που διατηρεί, ανανεώνει, κτλ πληροφορία (ένα object συγκεκριμένου τύπου) και ο προορισμός είναι ένα SL control. Όταν όλα είναι δηλωμένα σωστά, τότε όταν το CLR αντικείμενο αλλάζει με κάποιο τρόπο, μπορει να παρέχει ειδοποιήσεις για το τι άλλαξε, ποιό CLR property δηλαδή και όσα SL controls (elements) έχουν γίνει databound εκεί, μπορούν να ανανεώσουν τις τιμές τους. Η πρώτη επαφή με το DataBinding συνήθως έρχεται μέσα από τη XAML.

Through Expression Blend

Ας δούμε ένα μικρό παράδειγμα πριν πούμε τα βασικά του DataBinding και μάλιστα μέσα από το Blend, , ώστε να το γνωρίσουμε και αυτό το πολύτιμο εργαλείο καλύτερα. Έστω ότι θέλω να φτιάξω μία εφαρμογή για rating ταινιών (SL-IMDB δηλαδή), γρήγορα, τακτοποιημένα, μέσα από το Blend. Θέλω τα δεδομένα μου στην οθόνη να απεικονίζονται με βάση κάποιο συγκεκριμένο CLR Object, το οποίο μου έρχεται από τη βάση δεδομένων. Φτιάχνω ένα νέο silverlight project και για αρχή το object μου. Add New Item και φτιάχνω μία κλάση movie, που την γράφω κατευθείαν μέσα από το Blend έχοντας κανονικά Intellisense. Όχι ότι θα αναπτύξω κώδικα για το SL project στο Blend, αλλά μας δίνεται αυτή η ευκολία, χωρίς να χρειαζόμαστε πάντα και VS Studio και Expression Blend αναγκαστικά για κάποια μικροαλλαγή. Στη συνέχεια φτιάχνω μία καινούρια Page, ας χρησιμοποιήσουμε την ήδη υπάρχουσα MainPage.xaml. Ας δημιουργήσουμε και Sample Data στα γρήγορα για να βλέπουμε τη φτιάχνουμε.

Πηγαίνουμε δεξιά, πατάμε τη καρτέλα Data και Create διαλέγοντας την κλάση που θα κάνουμε populate με τυχαία (τα κλασσικά λατινικά) δεδομένα.

image

imageimageΜετά κάνοντας Drag το Movie στο Layout Root, έχω στο DataContext μου τυχαία δεδομένα, να κοιτάζω. Έχω τρεις περιοχές στην παραπάνω εικόνα. Έναν τίτλο (πάνω αριστερά), rating, περιγραφή και μία εικόνα, κάτω από τον τίτλο, που δεν έγινε populate με sample data, καθώς ο τύπος είναι Uri και το Sample Data από μόνο του δεν παρήγαγε κάποιο Url για να δείξει. Έχοντας το DataContext μου για τη σελίδα ορισμένο σε ένα Movie CLR object, το Expression Blend μπορεί να μου δώσει την δυνατότητα να επιλέξω γραφικά ποιό control θα παίρνει από που, δεδομένα. Επιλέγω το Textbox λοιπόν και πατάω δεξιά στα common properties το Text. Τι θέλω; Να γεμίζει με δεδομένα από την κλάση μου, οπότε πηγαίνω στα advanced properties και επιλέγω το DataContext tab. Εκεί θα βρω τα properties από τη Movie Class μου, και επιλέγω το Title. Κάνω το ίδιο και για τα υπόλοιπα. Στο Rating όμως θέλω και κάτι ακόμα. Όπως το Imdb. Να δείχνει μεν το μέσο όρο αλλά να μπορώ και εγώ να επιλέξω τιμή που αργότερα (θα συνυπολογιστεί στο ολικό μέσο όρο). Οπότε έχουμε μία διαφοροποίηση. Δεν θέλω η τιμή του να έρχεται στο UI μόνο, αλλά να επιλέξω κάτι εγώ και να κληθεί ο setter στο αντίστοιχο property επίσης. Επιλέγουμε λοιπόν το mode και το θέτουμε σε Two Way, όπως φαίνεται στο screenshot. Το μόνο που μένει για το demo μας είναι να θέσουμε με κάποιο τρόπο το DataContext της σελίδας μας σε ένα νέο Movie instance. Αυτό συνήθως το διαχειρίζεται το MVVM καλά, αλλά για την περίσταση μας αρκεί στο construction της σελίδας να γράψουμε ένα

 

public MainPage()         {             // Required to initialize variables             InitializeComponent();             this.DataContext = new Movie             {                 Title = "The Hustler",                 Rating = 0.85,                 Description = "The Hustler is a 1961 American drama film directed by Robert Rossen from the 1959 novel of the same name he and Sidney Carroll adapted for the screen. It tells the story of small-time pool hustler Fast Eddie. Felson and his desire to prove himself the best player in the country by beating legendary pool player Minnesota Fats. After initially losing to Fats and getting involved with unscrupulous manager Bert Gordon, Eddie returns to beat Fats, but only after paying a terrible personal price. The film was shot on location in New York City. It stars Paul Newman as Eddie Felson, Jackie Gleason as Minnesota Fats, Piper Laurie as Sarah, and George C. Scott as Bert.",                 Poster = new Uri("http://i23.photobucket.com/albums/b352/grnemo/the-hustler-paul-newman-jackie-gleason1.jpg")             };         }

Το αποτέλεσμα είναι το παρακάτω, έχοντας πάρει τις τιμές για το Silverlight application μας από κάποιο object του model μας. Ανοίγοντας το project από το Visual Studio, μας ζητάει να ενεργοποιήσουμε στο web config το debugging. Ανοίγουμε την κλάση μας, βαζουμε ένα breakpoint στον setter στο Rating και πατάμε τη δική μας βαθμολογία. Βλέπουμε ότι καλείται ο setter με τη νέα τιμή.

image  image

Έστω ότι θέλω να έχω μία λίστα από ταινίες, ή αντικείμενα και να βλέπω την περιγραφή τους σε παρόμοιο layout. Το DataBinding από την έκδοση 3 του silverlight έχει ένα feature με το οποίο μπορούμε να συνδέσουμε, controls μεταξύ τους, με καταγραφή μόνο των controls που έχουν κάποια τιμή που μας ενδιαφέρει. Θέλουμε λοιπόν, όποτε αλλάζουμε το SelectedItem από τη λίστα, να αλλάζουμε properties σε άλλα controls. Για τις ανάγκες του demo, έφτιαξα ένα demo datasource από το ίδιο menu όπως και πριν (ορίζοντας τον τύπο των dummy δεδομένων μου, ακόμα και της εικόνας), έφτιαξα ένα ListBox και έκανα drag and drop το property name, από το Data Context παράθυρο στο ListBox που ήθελα, όπως φαίνεται στην εικόνα. Στα επιμέρους controls, έβαλα bindings από το Expression Blend, με το ίδιο τρόπο. Επιλέγοντας την τελίτσα δεξιά από το property του κάθε control στο common properties, διαλέγοντας αυτή τη φορά, το tab, Element Property, επιλέγοντας το scene element που με ενδιαφέρει και ορίζοντας το property στο custom path expression. To αποτέλεσμα φαίνεται στην παρακάτω εικόνα.

imageimage

Revealing XAML

Αφού είχαμε το πρώτο μας Demo στο expression blend, ας δούμε τι γίνεται στη XAML που παράγεται από κάτω. Η αλήθεια είναι ότι αρκετές φορές όταν σχεδιάζεται μία σελίδα θα χρειαστεί να γράψετε αυτούσια XAML με το χέρι. Η XAML ορίζει τo λεγόμενo binding markup extension, όπου για το Silverlight (στην τρέχουσα version, 4)  είναι ένα από τα, Binding, StaticResource, TemplateBinding, RelativeSource. Εμάς μας ενδιαφέρει εκείνο το extension που λέγεται binding. Όπως κάθε extension έτσι και το Binding ορίζεται μέσα σε “{” “}” και προσδιορίζει το binding expression μεταξύ εκείνου του Dependency property και του property κάποιου CLR Object. Στην εφαρμογή μας ο τίτλος, η περιγραφή, το poster και το rating έχουν την παρακάτω απεικόνιση σε XAML:

      <TextBlock Text="{Binding Title}" …. />       <TextBlock Text="{Binding Description}" …. />       <Image>                 <Image.Source>                     <BitmapImage UriSource="{Binding Poster}"/>                 </Image.Source>       </Image>       <toolkit:Rating ItemCount="10" Value="{Binding Rating, Mode=TwoWay}"  />

Στo rating, που θέλαμε, όχι μόνο να διαβάζουμε τιμές από το object και να τις απεικονίζουμε, αλλά και να θέτουμε τιμές, πρέπει να δηλώσουμε ότι το DataBinding αφορά και data entry. Αυτό γίνεται με το Mode=TwoWay.

Στη συνέχεια μετασχηματίσαμε το projectaki ώστε να υποστηρίζει selection από listbox και τα αντίστοιχα controls να γεμίζουν πληροφορία, από το Data Context του selected item του Listbox. Το Binding expression είναι διαφορετικό ως εξής. Ας πάρουμε το Image Control για την απεικόνιση του Poster. Δηλώνουμε το element με ElementName και μετά το Path του property που μας ενδιαφέρει. Όμοια και για τα υπόλοιπα.

<Image Margin="236,110,258,178" Source="{Binding  ElementName=MovieList, Path=SelectedItem.Poster}"/>

Το ElementName, το Path και το Mode είναι τρία από τα features του Binding. Ακολουθεί μία λίστα και σύντομη περιγραφή από τα πιο συχνά χρησιμοποιούμενα. Μπορούν αν χρησιμοποιηθούν χωριζόμενα με κόμμα.

Property Περιγραφή
Path To property path που μας ενδιαφέρει από το object που γίνεται bound.
Converter Ένα object που αλλάζει την τιμή ενός object σε μία άλλη (συμβατή με το target dependency property). Υλοποιεί το IValueConverter. Για παράδειγμα. BooleanToVisibility, ή UrlToImage.
ElementName Για databinding μεταξύ elements.
FallbackValue Default τιμή για εμφάνιση αν αποτύχει το data binding.
RelativeSource Πραγματοποιείται bind, με object που βρίσκεται σε μία σχετική θέση (ιεραρχίας) με το target object.
StringFormat Για παραμετροποιήσιμη εμφάνιση των strings.
Source Χρήση όταν θέλουμε να κάνουμε bind με κάτι που δεν βρίσκεται στο DataContext. Για παράδειγμα,  {Binding Source={x:Static DateTime.Now}, Path=Day}

Αξίζει σε αυτό το σημείο να σημειώσουμε κάποια πράγματα. Για αρχή, εξ ορισμού το silverlight θα πετάξει exception αν χρησιμοποιηθούν δύο ή περισσότερα ταυτόχρονα, των Source, RelativeSource και ElementName, για ευνόητους λόγους.

Αξίζει να πούμε ένα παράδεγμα για τον πολύ χρήσιμο Converter.  O converter είναι ένα object που υλοποιεί δύο συναρτήσεις, μία για conversion του source object σε κάτι άλλο και μία την ανάποδη διαδικασία. Έστω ότι θέλουμε να χρησιμοποιήσουμε ένα string formatter έτσι ώστε, όταν έρχεται ένα string object από το source να μετασχηματίζεται σε κάτι άλλο.

<StackPanel> <HyperLink NavigateUri=”{Binding Uri, Converter={StaticResource StringToUriConverter}, ConverterParameter=’{0:d}’}“ /> </StackPanel>

Υλοποιούμε τον converter και τον κάνουμε reference ως static resource στο XAML μας, χρησιμοποιώντας το κανονικά.

public class StringToUriConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return new Uri(value as string); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }

Ένα πολύ ωραίο trick όταν βλέπετε τι σας έρχεται από τον binding μηχανισμό στο debug, είναι να κάνετε έναν DebugConverter, υλοποιώντας τον και κάνοντας ότι θέλετε μέσα του, ή απλά να τον έχετε για το πολύτιμo brakepoint που μπορείτε να βάλετε για να κάνετε inspect το value.

Code

Όταν γράφουμε στη XAML το binding, τότε στη πραγματικότητα το Silverlight δημιουργεί ένα instance της κλάσης Binding που ορίζεται στο System.Windows.Data και πραγματοποιεί αυτό ακριβώς το δέσιμο. Μας δίδεται και εμάς η δυνατότητα να πραγματοποιήσουμε δικά μας bindings in code .

<txtEcho Text="{Binding}" .../>

Binding myBinding = new Binding("Text"); myBinding.Source = txtRealBox;   txtEcho.SetBinding(System.Windows.Controls.TextBox.TextProperty, myBinding);

Η βασική προϋπόθεση για να παίξει το binding είναι η κλάση που μπαίνει στο datacontext να υλοποιεί το

public interface INotifyPropertyChanged {     event PropertyChangedEventHandler PropertyChanged; }

Και τα properties που γίνονται bound να κάνουν Raise όταν αυτά γίνονται set (o untyped τρόπος με magic-string στο propertyName).

public void NotifyPropertyChanged(string propertyName)     {         if (PropertyChanged != null)         {             PropertyChanged(this,                 new PropertyChangedEventArgs(propertyName));         }     }

To blog post για το MVVM (μία custom έκδοση δλδ, όπου το ViewModel γνώριζε το View - [Μαθαίνοντας Design Patterns] Model – View – ViewModel) κάνει χρήση αυτών των μηχανισμών.

Ας κλείσω με μία τυπική PIC από το msdn, που τα λέει όλα!

Posted: Δευτέρα, 18 Οκτωβρίου 2010 11:05 πμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , ,

Silverlight animatable custom controls: quick ‘n’ dirty!

Παρακάτω θα φτιάξουμε βήμα-βήμα ένα Notification box για το silverlight, χρησιμοποιώντας μία ελαφρώς παραλλαγμένη έκδοση του style που βρίσκεται ήδη στη νέα έκδοση Silverlight 4, που εισήγαγε το συγκεκριμένο control. Η λειτουργικότητα θα είναι επίσης ελαφρώς διαφορετική, αφήνοντας στην ευθύνη του silverlight control την λειτουργία του αυτόματου ελέγχου, για το αν υπάρχουν ή όχι notifications για τον χρήστη. Σκοπός είναι, καθώς θα καταγράφουμε βήμα βήμα την πορεία κατασκευής του control, θα καταλαβαίνουμε παράλληλα και τον κύκλο ζωής του. Θα κατασκευάσουμε λοιπόν, μία κλάση που θα κληρονομεί το Control, καθώς θα χρησιμοποιήσουμε Custom Control με Template. Πριν ξεκινήσουμε, αξίζει να σημείωσουμε ότι θα χρησιμοποιηθεί το live template για τον resharper, d(ependency) p(roperty), για γρηγορότερη κατασκευή των dependency properties που δίνουν αυτή τη τρελή ελευθερία στη διαδικασία ανάπτυξης rich εφαρμογών Silverlight.

Live Template

#region $NAME$ Property public $DECLARINGTYPE$ $NAME$ {     get { return ($DECLARINGTYPE$) GetValue($NAME$Property); }     set { SetValue($NAME$Property, value); } } public static readonly DependencyProperty $NAME$Property =     DependencyProperty.Register(         "$NAME$", typeof ($DECLARINGTYPE$), typeof ($DEPENDENCYOBJECT$),         new PropertyMetadata(On$NAME$Changed)); private static void On$NAME$Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {     $DEPENDENCYOBJECT$ me = d as $DEPENDENCYOBJECT$; } #endregion

Control Structure

Ξεκινώντας την κατασκευή του control, θα αποφασίσουμε τι θέλουμε να κάνουμε από πλευρά λειτουργικότητας. Θέλουμε να φτιάξουμε ένα Notification (toast) menu το οποίο θα κάνει pop up στο silverlight application μας, θα μας δείχνει μηνύματα και στο τελευταίο μήνυμα θα σβήνει. Ανά τακτά χρονικά διαστήματα θα πρέπει το control να τσεκάρει με συγκεκριμένο τρόπο για το αν υπάρχουν μηνύματα και αν ναι, να εμφανίζει το pop-up. Σχεδιαστικά το control θα πρέπει να υποστηρίζει δύο καταστάσεις, μία ενεργή και μία κρυμμένη (normal και hidden).

Θέλουμε να φτιάξουμε ένα δικό μας control λοιπόν. Έχουμε δύο μέρη. Ένα σχεδιαστικό και ένα προγραμματιστικό. Πρώτα το προγραμματιστικό. Κοιτάμε να δούμε τι θέλουμε πρώτα από το control μας, πράγμα που θα επηρρεάσει ποια κλάση κάνουμε inherit από τις παρακάτω:

System.Object
  System.Windows.DependencyObject
    System.Windows.UIElement
      System.Windows.FrameworkElement
        System.Windows.Controls.Control
          System.Windows.Controls.UserControl

Όλα τα controls κάνουν inherit από το DependencyObject που είναι η βασική κλάση που επεκτείνει τα CLR properties ενός object και τα κάνει να μπορούν να συμμετέχουν στο μηχανισμό διαχείρισης του silverlight (να τίθενται παραπάνω από μία φορά μέσω data binding). Το control αρχίζει και αποκτά visual υπόσταση, υπάρχουν δομές για διαχείριση input, καθώς και κάποια visual properties. To Framework Element μπορεί να συμμετέχει σε DataBinding μέσω του DataContext property που έχει, να μπορεί να ρυθμίζει το layout του, καθώς και παρέχει βασικά events για τον έλεγχο ζωής του control, όπως το LayoutUpdated, το Loaded, το Unloaded. Εμείς θα χρησιμοποιήσουμε το Control type, μιας και επιθυμούμε όχι μόνο να φτιάξουμε ένα control, αλλά να αφήσουμε τη δυνατότητα να μπορεί κάποιος να του αλλάξει τελείως το visual κομμάτι με ένα template. Το Control, δίνει αυτή τη δυνατότητα.

Φτιάχνουμε λοιπόν τη κλάση, η οποία θα έχει τα εξής Dependency Properties: Item source, για τη λίστα με τα αντικείμενα, ένα interval, ένα refreshcommand τύπου ICommand για τη συνάρτηση που εκτελείται ανα intervals, για να τσεκάρει αν υπάρχουν alerts. Χρησιμοποιούμε ICommand, το οποίο μας δίνει τη δυνατότητα να κάνουμε Databind, κάτι που να ελέγχει πότε μπορεί να εκτελεστεί (CanExecute()). Ένα message, ένα currentindex για να δείχνουμε πιο message διαβάζεται αυτή τη στιγμή, ένα IsOpen, για να καταλαβαίνουμε πότε είναι ανοικτό το notification και ένα total, για όλα τα μηνύματα. Αξίζει να δούμε ένα Dependency Propery πριν δούμε όλο το control. Ας παραθέσουμε το ItemSource. To Itemsource, είναι η εσωτερική λίστα με τα αντικείμενα που το κάθε ένα έχει μήνυμα που ενδιαφέρει το χρήστη. Στην παρούσα υλοποίηση έστω ότι το itemsource είναι object γενικότερα, το οποίο εμείς θα το χειριστούμε ως λίστα.

To Property που αφήνουμε το silverlight να μας το διαχειριστεί, φέρνοντάς μας τιμή όποτε αυτό είναι έτοιμο, παρομοίως και θέτει τιμή όποτε αυτό κρίνει. (Ο μηχανισμός των dp είναι σχεδιασμένος έτσι, ώστε αν ένα dp είναι data bound, συμμετέχει σε animation, κτλ)

public Object ItemsSource {     get { return (Object)GetValue(ItemsSourceProperty); }     set { SetValue(ItemsSourceProperty, value); } }

Εδώ είναι το πραγματικό μας dp. Έτσι δηλώνουμε, ότι το εν λόγω control έχει ένα dp ItemsSource, τύπου object, ο κάτοχος είναι το Alert Control καθώς και αν αλλάξει η τιμή του, κάνε κάποια πράγματα. Προσέξτε κάτι, το dependency property είναι static. Αυτό μας δείχνει ότι η δήλωση είναι ανα τύπο και όχι ανά στιγμιότυπο. Έτσι όταν κάνουμε register ένα dependency property δίνουμε όλα τα απαραίτητα στοιχεία, αλλά κατά το instantiation ενός control, δεν δεσμεύεται παρά μόνο ο απαραίτητος χώρος για όσα dependency properties ορίσουμε στη XAML. Επίσης ένα dp μη ξεχνάτε ότι μπορεί να γίνει και άλλα πράγματα όπως Data Binding, γράφοντας πχ. ItemSource = {Binding MyCollectionOnMyMVVMViewModel}.

public static readonly DependencyProperty ItemsSourceProperty =     DependencyProperty.Register(         "ItemsSource", typeof(Object), typeof(AlertControl),         new PropertyMetadata(OnItemsSourceChanged));

Όταν κληθεί το callback πρέπει πάντα να παίρνουμε το Dependency Object που έκανε raise to event (είπαμε είναι static η δήλωση).

private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {     AlertControl me = (AlertControl)d;     me.OnItemsSourceChanged((Object)e.OldValue, (Object)e.NewValue); }

Και να κάνουμε ότι θέλουμε βέβαια, όπως εγώ που τσεκάρω μία μεταβλητή και αλλάζω το Visual State, όπως θα δούμε παρακάτω.

private void OnItemsSourceChanged(Object oldValue, Object newValue) {     var enumerable = ItemsSource as IList;     if (enumerable == null) return;     Total = enumerable.OfType<Object>().Count();     if (Total > 0)     {         IsOpen = true;     }     ChangeVisualState(); }

Σε αυτό το σημείο είναι καλό να δούμε τον κώδικα των Dependency Properties μέσω reflector. Η DependencyProperty.Register κάνει κάποιους ελέγχους και στο τέλος καταλήγει απλά να αποθηκεύει το dependency property σε ένα dictionary, για το owner type:

internal static void RememberRegisteredProperty(string name, Type ownerType, DependencyProperty dp) {     Dictionary<string, DependencyProperty> dictionary;     if (!_registeredProperties.TryGetValue(ownerType, out dictionary))     {         dictionary = new Dictionary<string, DependencyProperty>();         _registeredProperties[ownerType] = dictionary;     }     dictionary[name] = dp;     if (dp.m_nKnownId == 0)     {         if (_customProperties.Count == 0)         {             _customProperties.Add(null);         }         dp.m_nKnownId = (uint) _customProperties.Count;         _customProperties.Add(dp);     } }

Style

To style είναι ένας τρόπος να ορίσουμε τις μεταβλητές διάφορων dependency properties κάποιου element πακεταρισμένα. Με ένα style υπάρχει η δυνατότητα να πακετάρουμε τιμές που αλλάζουν το visual feeling πολλών elements. Να θυμάστε ότι ορίζοντας τιμές σε containers οι τιμές θα οριστούν και στα παιδιά.

Έστω ότι έχουμε ένα style για το control μας, λοιπόν και θέλουμε οι default τιμές να ορίζονται στο generic.xaml. Θα γράψουμε λοιπόν ένα πακετάκι που θα λέει:

<Style TargetType="local:AlertControl">   <Setter Property="Height" Value="100"/>   <Setter Property="Width" Value="350"/>   <Setter Property="HorizontalAlignment" Value="Right"/>   <Setter Property="Margin" Value="0,0,30,0"/>   <Setter Property="VerticalAlignment" Value="Bottom"/> </Style>

Μία απλή λεπτομέρεια είναι ότι για να δεθεί το control με το default style χρειάζονται δύο πράγματα. Ένα το targetType και δύο το property του control μας (ως Control) DefaultStyleKey που λέει το αντίστοιχο πράγμα: DefaultStyleKey = typeof(AlertControl); Υπάρχουν πάρα πολύ ωραία πράγματα που μπορούμε να κάνουμε με τα styles, όπως style inheritance κτλ, αλλά ας προχωρήσουμε

Template

Το silverlight μας παρέχει τη δυνατότητα να αλλάξουμε τελείως το visual “face” ενός control, η να ορίσουμε ένα default με τον ίδιο τρόπο. Όταν δηλαδή, καταλάβετε ότι δεν μπορείτε να φέρετε στα μέτρα σας, ένα control, τότε φτιάξτε το δικό σας template (χωρίς να δημιουργήσετε ένα ολοκαίνουριο control). Το Template είναι απλά ένα ακόμα DependencyProperty, που το χρησιμοποιεί το silverlight για να χτίσει το visual tree, και να το παρουσιάσει. Μπορεί να οριστεί είτε μέσα στον κώδικα

<Button Content=”Yo, I am a Custom Control”>     <Button.Template>         <ControlTemplate TargetType=”Button”>             <Border…>             <Textbox…>             <AnotherCustomControl…>         </ControlTemplate >     </Button.Template> </Button>

είτε μέσα από ένα style, ορίζοντας ένα ακόμα dependency property όπως όλα τα υπόλοιπα μέσα από το προκαθορισμένο syntax του style tag.

<Setter Property="Template">     <Setter.Value>         <ControlTemplate  TargetType="local:AlertControl">             <Grid HorizontalAlignment="Stretch" x:Name="LayoutRoot" VerticalAlignment="Stretch">                 <Grid.Resources>                     <converters:ObjectToStringConverter x:Key="ObjectToStringConverter"/>                 </Grid.Resources>                     <Border x:Name="PopUp" Opacity="0" Visibility="Collapsed" >                                                <StackPanel Orientation="Vertical">                         <local:SynchedSoundPlayer x:Name="NotificationSound" HorizontalAlignment="Left" VerticalAlignment="Top" Source="/NameSpace;component/Resources/Sounds/notification.mp3" Visibility="Collapsed" AnimatablePlay="0"/>                         <Border Height="24" CornerRadius="4" >                             <Border.Background>                                <LinearGradientBrush StartPoint="0.5,0.0" EndPoint="0.5,1.0">                                     <GradientStop Offset="0.2" Color="#FF1C68A0" />                                     <GradientStop Offset="1.0" Color="#FF54A7E2" />                                </LinearGradientBrush>                             </Border.Background>                             <Border.Effect>                                 <DropShadowEffect BlurRadius="4" ShadowDepth="4" Opacity="0.4" />                             </Border.Effect>                             <TextBlock Text="Notification" FontSize="12" FontWeight="Bold" Foreground="White" Margin="4" />                         </Border>                         <Grid Background="LightYellow" Height="{TemplateBinding Height}" MinHeight="70" >                             <Grid.Effect>                                 <DropShadowEffect BlurRadius="4" ShadowDepth="4" Opacity="0.4" />                             </Grid.Effect>                             <Grid.ColumnDefinitions>                                 <ColumnDefinition Width="Auto"/>                                 <ColumnDefinition Width="*"/>                                </Grid.ColumnDefinitions>                             <Grid.RowDefinitions>                                 <RowDefinition Height="Auto"/>                                 <RowDefinition Height="Auto"/>                             </Grid.RowDefinitions>                             <Image Grid.Column="0" Grid.Row="0" Source="/NameSpace;component/Resources/Images/refresh.png" Width="32" Height="34" Stretch="Fill" Margin="4" VerticalAlignment="Top" />                             <TextBlock Grid.Column="1" Grid.Row="0" x:Name="Message" Text="{TemplateBinding Message}" TextWrapping="Wrap"/>                             <StackPanel Grid.Column="1" Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="4">                                 <TextBlock x:Name="CurrentIndex" DataContext="{TemplateBinding CurrentIndex}" Text="{Binding Converter={StaticResource ObjectToStringConverter}}" />                                 <TextBlock Text=" / "/>                                 <TextBlock DataContext="{TemplateBinding Total}" Text="{Binding Converter={StaticResource ObjectToStringConverter}}" />                             </StackPanel>                         </Grid>                     </StackPanel>                 </Border>             </Grid>         </ControlTemplate>     </Setter.Value> </Setter>

Θυμηθείτε ότι με το style ορίζουμε τιμές σε Dependency Properties πακεταρισμένα, με τα templates ορίζουμε visual tree, αλλά πώς περνάμε τιμές από το style μας, ή τιμές που έχουν περάσει από τον ορισμό του control, μέσα στα elements που ορίζουμε στο Control Template? Μέσω Template Binding φυσικά. Δείτε το παραπάνω και θα καταλάβετε.

Visual State Manager

H πηγή των animations στο silverlight είναι το storyboard. Τι γίνεται δηλαδή σε ένα control όταν αλληλεπιδρούμε πάνω του και πώς γίνεται τι. Εξωτερικά και συνοπτικά, ένα control έχει καταστάσεις και μεταβάσεις, δηλαδή ένα button μπορεί να είναι pressed, focused, normal, επίσης η μετάβαση από το normal στο focused περιλαμβάνει το highlight του περιγράμματος, ή όταν είναι pressed φαίνεται πιο σκοτεινό και ελαφρώς πιο μικρό ώστε να φαίνεται πατημένο. Στην περίπτωση μου θέλω να είναι σε Normal και Hidden. Η μετάβαση να γίνεται ομαλά αλλάζοντας κάποια τιμή όπως το Visibility και το Opacity σταδιακά για να κάνει ένα ωραίο fade in όταν εμφανίζεται. Αυτά ορίζονται στη XAML και το παρακάτω κομμάτι κώδικα μιας και αφορά το control μας μπαίνει κανονικά στο template που φτιάξαμε παραπάνω.

<VisualStateManager.VisualStateGroups>                             <VisualStateGroup x:Name="VisualStateGroup">                                 <VisualState x:Name="Normal">                                     <Storyboard x:Name="FaderStory">                                         <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PopUp" Storyboard.TargetProperty="Visibility">                                             <DiscreteObjectKeyFrame KeyTime="00:00:00.5000000" Value="Visible"/>                                         </ObjectAnimationUsingKeyFrames>                                         <DoubleAnimation BeginTime="00:00:00.5000000" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="PopUp" From="0.0" To="1.0" Duration="0:0:1" />                                         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="NotificationSound" Storyboard.TargetProperty="(SynchedSoundPlayer.AnimatablePlay)">                                             <DiscreteDoubleKeyFrame KeyTime="0" Value="1"/>                                         </DoubleAnimationUsingKeyFrames>                                     </Storyboard>                                 </VisualState>                                 <VisualState x:Name="Hidden">                                     <Storyboard x:Name="HideFaderStory">                                         <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="PopUp">                                             <EasingDoubleKeyFrame KeyTime="0" Value="0"/>                                         </DoubleAnimationUsingKeyFrames>                                         <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PopUp" Storyboard.TargetProperty="Visibility">                                             <DiscreteObjectKeyFrame KeyTime="00:00:00.5000000" Value="Collapsed"/>                                         </ObjectAnimationUsingKeyFrames>                                     </Storyboard>                                 </VisualState>                             </VisualStateGroup>                         </VisualStateManager.VisualStateGroups>

Προφανώς υπάρχουν πάρα πολλά είδη μεταβάσεων από μία τιμή στην άλλη. Έστω ότι θέλουμε να αλλάξουμε το height δηλαδή. Δεν χρειάζεται να το κάνουμε μέσα σε 5 δευτερόλεπτα να αλλάζει ισομοιρασμένα τιμές (γραμμικά, δηλαδή), αλλά να κάνει σαν να αναπηδά. Να κάνει bounceEase όπως λέμε (φανταστείτε το σαν συνάρτηση t, f(t) )

BounceEase

IC270154

To πότε αλλάζει state το control μας έχει να κάνει με τη λογική του control μας. Μέσα στον κώδικα λοιπόν, έχω τη λογική μου να τσεκάρω κάθε 15 δευτερόλεπτα αν υπάρχουν καινούρια alerts για τον χρήστη μου και αν βρω ότι υπάρχουν, γράφω ένα

VisualStateManager.GoToState(this, "Normal", true);

image

και παρατηρώ τη μαγεία να ενεργοποιείται. Φανταστείτε να έχετε έναν designer που να σας έχει φτιάξει το υπερτέλειο animation και να το χειρίζεστε εσείς, από κώδικα (σε μία line of business application, ή σε ένα παιχνιδάκι σε silverlight).

Τελικά έχουμε κώδικα που κάνει compile και η xaml μας ερμηνεύει όμορφα ότι γράψαμε και όλα παίζουν. Τι συμβαίνει όμως κατά σειρά;

Lifecycle

Ας πούμε σε γενικές γραμμές τι γίνεται όταν parsarεται το XAML αρχείο. Όταν ανιχνευθεί το opening tag, καλείται ο constructor του control, γίνονται wire κάποιοι handlers, τίθεται το DefaultStyleKey αν υπάρχει και συνεχίζουμε. Αν υπάρχει Style, που τίθεται πχ με StaticResource, τότε καλούνται όσοι setters ορίζονται στο style και τίθενται τα values. Στη συνέχεια, γίνεται apply το style στο generic.xaml (χωρίς να κάνει override τιμές). Συνεχίζει ο parser και συναντά properties, που είναι explicitely setted. Σε αυτό το σημείο το element (control), προστίθεται στο Visual Tree και γίνεται raise το Loaded event. Αν το control δεν έχει visual tree, αναλαμβάνει δράση το Template, που ορίζει πώς θα σχεδιαστεί το control. Αυτό γίνεται με κλήση στην ApplyTemplate function. Όταν γίνει apply το template, μπορούμε να παρέμβουμε εμείς κάνοντας override την OnApplyTemplate. Τι μπορούμε να κάνουμε, εξαρτάται πραγματικά από το τι θέλουμε. Σε αυτό το σημείο γνωρίζουμε τα controls τα οποία απαρτίζουν την υπόσταση του control στο Visual Tree. Μπορούμε λοιπόν, να παρέμβουμε σε αυτό το Visual Tree, πριν αποσταλλεί για παρουσίαση στο χρήστη και να κάνουμε fine tuning κάποια πράγματα (όπως να κάνουμε wire up κάποιους handlers για interraction). Επίσης γνωρίζοντας τα control, μπορούμε να αποκτήσουμε references σε αυτά (GetTemplateChild) και να κάνουμε διάφορα πράγματα. Αν για παράδειγμα υπάρχει ένα drop down, μπορούμε να πάρουμε reference σε αυτό και να του κάνουμε hook κάποιους handlers. Στη συνέχεια καλούνται οι MeasureOverride και ArrangeOverride, που ορίζουν το μέγεθος και τη θέση των controls.

 

Σε αυτό το post:

  1. Κατέγραψα εν συντομία μία περιγραφή των custom controls
  2. Κατέγραψα μερικά πράγματα στα γρήγορα σχετικά με το lifecycle ενός control
  3. Δημιουργήσαμε μία custom έκδοση του Notification Control που έχει το silverlight 4 με δικά μας χαρακτηριστικά αλλά με παρόμοιο visual feeling, εκτός του ότι μπορεί να παίξει ήχο μέσα στο storyboard (an idea from http://forums.silverlight.net/forums/p/127010/285086.aspx ), παίρνει μία custom λογική από dependency property (ένα object τύπου ICommand που μπορεί να γίνει bind στο ViewModel (Presentation Model) μας και να το ορίσουμε στο View) και έχει κάτι σαν paging.

Το παραπάνω control μπορεί να χρησιμοποιηθεί δηλαδή ως:

<myControls:AlertControl x:Name="Notifications" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Grid.RowSpan="2"                             RefreshCommand="{Binding RefreshAlertsCommand}"                             ItemsSource="{Binding AlertInstances}"/>

Play with some code: http://cid-c6b2e583e302124e.office.live.com/self.aspx/Public/AlertControl.zip

Posted: Παρασκευή, 17 Σεπτεμβρίου 2010 11:00 μμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: ,

[Μαθαίνοντας Design Patterns] Model – View – ViewModel

Συνεχίζοντας τη σειρά με τα design patterns, σε αυτό το post γίνεται μία αναφορά στο MVVM, μέσα από μία αρκετά απλοϊκή προσέγγιση. Θα περιγραφεί το pattern, θα δωθούν μερικές base classes, ένα utility για το πώς γίνεται type-safely raise ένα event για ανανέωση UI, καθώς και ένα μικρό παράδειγμα, με Bing Maps σε μία Silverlight εφαρμογή.

Τί είναι το MVVM;

Το Model – View – ViewModel είναι ένα πρότυπο σχεδίασης για το σχεδιασμό διεπαφών χρήστη, το οποίο έχει επηρροές τόσο από το Model View Presenter, όσο και από το Model View Controller. Είναι γνωστό  και ως Presentation Model (σχεδόν ίδιο)(PM στο εξής), όπως το έχει καταγράψει ο Martin Fowler. To Model – View – ViewModel (MVVM στο εξής) είναι επίσης ένας καθιερωμένος τρόπος, για τη δημιουργία εφαρμογών που βασίζονται και σε XAML και επιλέχθηκε για την ευελιξία που παρέχει στον προγραμματιστή κατά την ανάπτυξη (decoupling View-Model). Το design pattern που περιγράφεται στη συνέχεια με τη βοήθεια του παραδείγματος σε silverlight, παρουσιάζεται έχοντας κατα νου το ισχυρό πλαίσιο databinding του silverlight (η αντιστοιχία στοιχείου οθόνης με κανονικό Property ενός object).

Ξεκινώντας την περιγραφή του MVVM, αναφέρουμε ότι παρέχει (όπως και το PM) ένα επίπεδο αφαίρεσης του View (της εικόνας δηλαδή που φαίνεται στον χρήστη / XAML) στο οποίο ορίζεται τόσο η κατάστασή του, όσο και η συμπεριφορά του. Έστω ότι έχουμε την οθόνη διαχείρισης καταστημάτων μίας εταιρίας πώλησης παπουτσιών. Υπάρχουν δύο εκδόσεις για την εν λόγω οθόνη, μία που παρουσιάζει όλα τα καταστήματα (datagrid με μία λίστα) και μία που ενεργοποιείται όταν αλλάζουμε ιδιότητες από κάποιο κατάστημα (αρκετά controls, όπως textboxes κτλ με τα οποία μπορούμε να καταχωρούμε πληροφορίες για ένα selected store από την προηγούμενη σελίδα). Οι δύο οθόνες λέγονται αντίστοιχα Master και Detail σελίδες. Έστω ότι έχουμε την Detail σελίδα να αναπτύξουμε. Στη συνέχεια περιγράφεται πώς το MVVM μπορεί να διαχωρίσει τις αρμοδιότητες, για διαφορετικές ενέργειες που λαμβάνουν χώρα πίσω από την οθόνη.

Τα τρία στοιχεία που περιγράφονται είναι τρία διαφορετικά αντικείμενα στη μνήμη και έχουν πεδία και μεθόδους. Έτσι λοιπόν έχουμε το View (τονίζω πάλι ότι αναφερόμαστε σε XAML αρχείο) το οποίο περιέχει διάφορα controls με τα οποία μπορεί να αλληλεπιδράσει ο χρήστης. Κάθε control που περιέχεται, μπορεί να εμφανίσει πληροφορία είτε σε μορφή κειμένου είτε σε κάτι πιο πολυμεσικό.

Έστω ότι έχουμε ένα πεδίο κειμένου (textboxes) και έναν επιλογέα με μία μοναδική επιλογή κάθε φορά (combobox). Τα δύο αυτά controls, δένουν τις τιμές τους με ιδιότητες (properties) του ViewModel μέσω μηχανισμού databinding. Προγραμματιστικά το databinding ανάμεσα στο View και στο ViewModel (που περιέχει όλη την πληροφορία που δείχνει/ανανεώνει/… το View) γίνεται πάρα πολύ εύκολα, μιας και θέτουμε το ViewModel αντικείμενο, ως το πλαίσιο δεδομένων (datacontext) του View (βλ. παράδειγμα). Αν κάποια τιμή, σε κάποιο property αλλάξει, τότε η ίδια ιδιότητα ενεργοποιεί ένα γεγονός (κάνει raise ένα event propertychanged) που υποδηλώνει ότι άλλαξε τιμή και έτσι ενημερώνει το View ότι άλλαξε η τιμή για το συγκριμένο user control (για παράδειγμα ένα textbox – πεδίο εισαγωγής κειμένου). Κάθε πληροφορία δηλαδή, που είναι databound με ένα control όταν αλλάξει, ενημερώνει το UI ότι άλλαξε και ότι θα πρέπει να ανανεωθεί. Για να μπορεί ένα αντικείμενο (το ViewModel στην περίπτωσή μας) να αλληλεπιδρά με αυτόν τον τρόπο θα πρέπει να υλοποιεί το INotifyPropertyChanged interface. Έτσι λοιπόν ανανεώνεται και η αντίστοιχη πληροφορία στον χρήστη. Σε αντίθετη περίπτωση, όταν ένας χρήστης πατήσει ένα κουμπί, μία εντολή (Commanding) θα εκτελεστεί στο ViewModel και θα πραγματοποιήσει όλες τις αλλαγές στα δεδομένα του μοντέλου (τα οποία περιέχονται στο ίδιο το αντικείμενο του ViewModel).

To Model είναι το αντικείμενο το οποίο καλείται από το ViewModel για λειτουργίες πρόσβασης στα δεδομένα. Στην περίπτωσή μας έχουμε δομικές μονάδες που περιγράφουν το πεδίο της εφαρμογής (customers, orders, κτλ). Αξίζει να κρατήσουμε τρία πράγματα: image

 

  • To View ΔΕΝ γνωρίζει το Model (γιατί δεν απαιτείται κάτι τέτοιο εννοιολογικά)
  • To ViewModel ΔΕΝ γνωρίζει τίποτα για View (εξαρτάται, ίσως είναι θέμα προθέσεων του προγραμματιστή)
  • To Model ΔΕΝ γνωρίζει τίποτα για View

Στις XAML εφαρμογές, το View είναι η οθόνη που σχεδιάζουμε στο Expression Blend (την εφαρμογή της Microsoft για σχεδιασμό σελίδων), το ViewModel αναπτύσσεται στο Visual Studio και το Model είναι στην ουσία οι κλάσεις που χρησιμοποιούμε για το πεδίο της εφαρμογής μας οι οποίες περιέχουν όλες τις ιδιότητες οι οποίες υπάρχουν στο αντικείμενο. Στις RIA εφαρμογές το κανονικό μοντέλο υπάρχει στον server και μία πιο μινιμαλιστική έκδοση στον client, για να μπορεί να διαχειρίζεται τα αντικείμενα. Για παράδειγμα στην σελίδα με την επεξεργασία ενός στιγμιότυπου της οντότητας Shoe (π.χ. για rowId = 5) το ViewModel έχει ως ένα από τα πεδία του, ένα αντικείμενο τύπου shoe, το οποίο δηλώνεται στο Model. To Model, έχει στον server την κανονική έκδοση της οντότητας Shoe, ορίζοντας πρόσθετες ιδιότητες αν είναι απαραίτητο (που δεν εμφανίζονται στον client).

Στις ακόλουθες ιεραρχίες μπορούμε να δούμε την κλάση ViewControl που είναι στην ουσία η οθόνη μας, που γνωρίζει το ViewModel και το έχει θέσει ως το Data Context της. Υπάρχει ένα κάπως well defined API μέσα από generic type constraints.

 

Base Classes

public class ViewControl : UserControl, IView {     private IViewModel viewModel;     public ViewControl()     {     }     protected ViewControl(IViewModel viewModel) : this()     {         SetViewModelInternal(viewModel);     }     public virtual TViewModel GetViewModel<TViewModel>()         where TViewModel : class, IViewModel     {         return this.viewModel as TViewModel;     }     public virtual void SetViewModel(IViewModel model)     {         this.SetViewModelInternal(model);     }     private void SetViewModelInternal(IViewModel model)     {         this.viewModel = model;         this.DataContext = model;         PropertyChanged.Raise(() => DataContext);     }     #region INotifyPropertyChanged Members     public event PropertyChangedEventHandler PropertyChanged;     #endregion } public interface IView : INotifyPropertyChanged { } public interface IView<TViewModel> : IView     where TViewModel : IViewModel {     TViewModel ViewModel { get; set; } } public interface IViewModel : INotifyPropertyChanged { } public interface IViewModel<TView> : IViewModel     where TView : IView {     TView View { get; set; } } public abstract class ViewModel<TView>     where TView : IView {     public bool IsDesignTime     {         get         {             return (Application.Current == null) || (Application.Current.GetType() == typeof(Application));         }     }     #region INotifyPropertyChanged Members     public event PropertyChangedEventHandler PropertyChanged;     #endregion }

Έστω η περίπτωση που έχω controls που στέλνουν εντολές, buttons, κλπ κλπ. Θα πρέπει να υπάρχει μία δομή στο XAML, που θα δηλώνει το πια εντολή είναι αυτή που θα πρέπει να εκτελεστεί όταν πατηθεί το κουμπί. Στην περίπτωση του code behind μπορούμε να παίξουμε με handlers αλλά με MVVM μπορώ να έχω μία διαφορετική προσέγγιση. Γιατί να μη εκμεταλλευτώ το μοντέλο του MVVM και να “βρωμίσω” το code behind μου με κώδικα hard-wired με το View? Έτσι λοιπόν ένα σημείο κλειδί είναι να χρησιμοποιήσουμε Commands. Στο WPF υπάρχουν πραγματικά commands που σε συνδυασμό με τα routed events δημιουργούν την έννοια του routed command, αλλά δεν θα μιλήσουμε εδώ γι’ αυτό. Στο Silverlight (3) υπάρχει το ICommand interface το οποίο είναι στην ουσία το εξής, και θα αναλύσουμε σε κάποια άλλη συζήτηση για το commanding σε MVVM:

interface ICommand {     void Execute(object parameter);     bool CanExecute(object parameter);     event EventHandler CanExecuteChanged; }

 

Bing Maps MVVM

Στο συγκεκριμένο παράδειγμα παραθέτω το ViewModel μίας οθόνης που έχει ένα Bing Maps control. Θέλω σε αυτόν το χάρτη, να παρουσιάσω καταστήματα που έχουν χωρική πληροφορία και θα εμφανιστούν στον χάρτη μας. Τι χρειάζομαι ως business πληροφορία; Ένα collection από καταστήματα (τύπου ObservableCollection που μπορεί να ενημερώνει όταν επιδέχεται αλλαγές) και ίσως ένα SelectedStore για να γνωρίζω πιο store έχει επιλεγχθεί (για απεικόνιση περισσότερων πληροφοριών).

public class StoreMapViewModel : ViewModel<IStoreMapView>, IStoreMapViewModel    {        private BusinessLogicDomainContext domainContext = new BusinessLogicDomainContext();        public StoreMapViewModel()        {            Load();        }        private ObservableCollection<Store> stores;        public ObservableCollection<Store> Stores        {            get            {                return stores;            }            set            {                stores = value;                PropertyChanged.Raise(() => Stores);            }        }        private Store selectedStore;        public Store SelectedStore        {            set            {                selectedStore = value;                PropertyChanged.Raise(() => SelectedStore);            }            get            {                return selectedStore;            }        }        private void Load()        {           //Εδώ υπάρχει κώδικας για ανάκτηση όλων των stores. Χρησιμοποιήθηκε το WCF Ria Services κομμάτι του WCF           //το οποίο σχεδιάστηκε ειδικά για τις ανάγκες Line of Business silverlight applications            LoadOperation<Store> loadedStores = this.domainContext.Load(this.domainContext.GetStoresQuery());            loadedStores.Completed += (sender, e) => { Stores = new ObservableCollection<Store>(domainContext.Stores); };        }        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;    }

Αναφορικά με το όμορφο API για το raising των property changed events, προκειμένου να γράφουμε κάτι του στυλ Raise(“PropertyName”), θεωρώ πιο όμορφo να χρησιμοποιήσω έναν type safe τρόπο με lambda που na παρέχει το property name, αντί να γράφω κάτι error prone όπως το όνομα του property με το χέρι. (Credits to original author)

public static void Raise(this PropertyChangedEventHandler handler, Expression<Func<object>> propertyExpression) if (handler != null) {     // Retreive lambda body     var body = propertyExpression.Body as MemberExpression;     if (body == null)         throw new ArgumentException("'propertyExpression' should be a member expression");      // Extract the right part (after "=>")     var vmExpression = body.Expression as ConstantExpression;     if (vmExpression == null)         throw new ArgumentException("'propertyExpression' body should be a constant expression");      // Create a reference to the calling object to pass it as the sender     LambdaExpression vmlambda = System.Linq.Expressions.Expression.Lambda(vmExpression);     Delegate vmFunc = vmlambda.Compile();     object vm = vmFunc.DynamicInvoke();      // Extract the name of the property to raise a change on     string propertyName = body.Member.Name;     var e = new PropertyChangedEventArgs(propertyName);     handler(vm, e); }

Bing Maps View

To View μας που περιέχει το bing maps control, είναι ένα μάθημα για databinding με XAML, templating κτλ από μόνο του, αλλά ήθελα να το δείξω εδώ για πληρότητα. Παρατηρήστε πόσο εύκολα και κατανοητά δηλώνω το που αντλώ πληροφορία για τα pushpins (Stores collection) και πιό member διαλέγω για να ανακτήσω την πληροφορία (LocationInformation).

<controls:ViewControl     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"     mc:Ignorable="d"     xmlns:controls="clr-namespace:Infrastructure.Controls;assembly=Infrastructure"     d:DesignWidth="640" d:DesignHeight="480"     x:Class="Views.StoreMapView">     <UserControl.Resources>         <DataTemplate x:Key="LogoTemplate">             <m:Pushpin m:MapLayer.Position="{Binding LocationInformation}" />         </DataTemplate>     </UserControl.Resources>     <Grid x:Name="LayoutRoot">         <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}" Margin="0,0,-38,0" >             <StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}">                 <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"                                    Text="{Binding Path=ApplicationStrings.StoreMapPageTitle, Source={StaticResource ResourceWrapper}}"/>                 <StackPanel x:Name="MapControl" Orientation="Horizontal">                     <m:Map x:Name="MyMap" CredentialsProvider=<!--Εδώ τοποθετούνται τα δικά μας credentials--> Width="519" HorizontalAlignment="Left">                                        <m:MapItemsControl x:Name="ListOfItems"                                     ItemTemplate="{StaticResource LogoTemplate}"                                     ItemsSource="{Binding Stores}">                         </m:MapItemsControl>                     </m:Map>                     <ComboBox Height="23" Name="comboBox1" Width="150" VerticalContentAlignment="Top"                                                           VerticalAlignment="Top" ItemsSource="{Binding Stores}" DisplayMemberPath="storeName"                                                           SelectionChanged="comboBox1_SelectionChanged" />                 </StackPanel>             </StackPanel>         </ScrollViewer>     </Grid> </controls:ViewControl>

 

Ελπίζω να έδωσα αρκετό food for thought. Enjoy Silverlight & MVVM!!!

Posted: Πέμπτη, 11 Μαρτίου 2010 2:30 πμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , , ,