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

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

Όλες οι Ετικέτε... » C#   (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 σχόλια
Δημοσίευση στην κατηγορία: ,

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 σχόλια
Δημοσίευση στην κατηγορία: ,

My live Q&A with Eric Lippert, Luke Hoban and Mads Torgersen at Tech·Ed 2010, North America

Yesterday I watched the live 3:30 PM: Languages Panel session, with Eric Lippert, Luke Hoban and Mads Torgersen and the niner Charles Torre, through (http://www.msteched.com/), provided by Channel9 live (http://channel9.msdn.com/). This wasn’t just a live panel, but an interactive QnA too, so as me and a friend / colleague of mine (Nikos), watched it, we asked a question through twitter of Channel9 (http://twitter.com/ch9live). The question (both actually) have been presented through the monitor, to the panel and was answered briefly (check screenshots). The following, are food for thought to everyone and anyone who would like to participate in a conversation about Types in C#, F#, Scala, Java, etc, provide clarifications or error corrections in the way I perceived the answers or else :):)

image

image

Mads Torgersen seemed pretty sure and his initial reaction was original when answering the question! A bright “No”, from the member of the C# Language, designer group (he was speaking of his main domain of work, C#).

Take LINQ for example, he said. LINQ is hardwired in the compiler, providing of this query pattern of composed Select, Where, From, … etc clauses, that provide a single type of computation, over our data. How can one, express through type system, such compositions? The answer is Higher Kind Types. Mads said, that providing such functionality to the C# programmer, would be pretty confusing. There would be rare cases, that one would like to define higher kind types[1], thus this capability (a concept coming from languages like Haskell btw), is not included in future plans currently.

LINQ construct is in general following the monadic [2] programming model. In contrast, F#, being a functional programming language and giving features like computational expressions for asynchronous programming, would be able to provide such level of abstraction if needed in the future. As I understood from what Luke Hoban said, this is imagemore “easy” in F#, as during the design process, such monadic constructs are already implemented in a general way by the F# language design group, hence, this could be exposed in the future.

 

 

The live panel, continued the conversation with very interesting topics, such as Rx, but when the “pure” word came up, I couldn’t resist posting another (and much more impulsive) question, that was answered live very quickly, from the panel.

image

 

 

 

 

 

 

 

 

 

 

 

Luke immediately took action and said that, many people are very active with Haskell inside the MS group, but currently isn’t any movement for incorporating .NET with a pure functional language, due to the incompatible paradigm of current .NET with FP. .NET is a chaos of libraries, doing so many things, providing ways of side effect-“ing” so much, that a pure functional language couldn’t fit in. However ideas from this whole class of functional languages, could migrate in the future, towards F#.

Mads noted something pretty accurate. Purity is also a programming style. This very same concept is very useful as a C# style too. Minimizing side effects, approaches this way of thinking, and we can simulate this in our own way (in a local manner). Mads, also said that this could be the way of .NET library transformation over the years, but not instantly.

I would like to thank the panel, for providing such elaborate answers! Keep up the very good work, providing us such expressive languages!!

[1] Kind is to a type what a type is to a value (I don’t think this is accurate, but it will do the job for now). So, conceptually, we have objects in the bottom. Objects are distinguished by types and types are classified by kinds. Starting again from bottom, objects are known at run time, types must be known at compile time and the kinds are specified by the language itself. Java 5 and C# 2.0 introduced first order parametric polymorphism, thus abstracting over types. The resulting type constructor cannot be further abstracted, however. In a generic way, you can say I want a list of T, where T is int... . So in List<T>, how would it be to abstract over that and specify through a parameter the List too via a Container[T] parameter for example? Thus we could parameterize more (abstract) List<T>, instantiating an M<T, Container> with int and List and getting List<int> or ArrayList<Customer>. So I have the M which takes a type and returns a type. Monads are generalized via this way in Haskell (check Simon Peyton Jones’ words here).

[2] Monad in simple words is a way (strategy), of combining computations to build other computations.

Posted: Πέμπτη, 10 Ιουνίου 2010 2:33 πμ από 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 σχόλια
Δημοσίευση στην κατηγορία: , , ,

Closures στη C#

Τελικά τα closures είναι ένα απλό concept, που όταν το κατανοήσει κάποιος, το βλέπει τετριμμένο μετά. Αν μπορούσα να δανειστώ από κάπου, δυό λόγια για ορισμό, νομίζω η wikipedia το περιγράφει αρκετά καλά. Τα closures είναι μία ιδέα των γλωσσών προγραμματισμού, που επιτρέπει, σε συναρτήσεις που αντιμετωπίζονται ως first-class objects, τον συσχετισμό ελεύθερων μεταβλητών τους, με τον λεκτικό περιβάλλοντα χώρο της συνάρτησης. Στη συνέχεια θα ήθελα να διατυπώσω το εν λόγω concept όσο πιο σύντομα γίνεται, δανειζόμενος (μερικώς ή πλήρως) κώδικα από το βιβλίο C# in Depth του Jon Skeet και από το Reference της C#.

First – class functions στην C#

First class functions είναι συναρτήσεις που έχουν όλες τις ιδιότητες των αντικειμένων, μπορούν να δημιουργηθούν δυναμικά, μπορούν να συμμετέχουν σε δομές δεδομένων, να ανατεθούν ως παράμετροι, καθώς και να επιστραφούν από κλήσεις άλλων συναρτήσεων. Στην οικογένεια των γλωσσών προγραμματισμού, η C# ανήκει σε εκείνες που υποστηρίζουν first class functions, μέσω του τύπου δεδομένων Delegate και όλης της πραγματικότητας γύρω από αυτό (anonymous methods, lambdas, expression trees). Θυμίζω ότι σε γενικές γραμμές Delegate είναι ένας τύπος δεδομένων (reference type) όπου παρέχει έναν ασφαλή τρόπο για να ενθυλακώσουμε συναρτήσεις (είτε static είτε instance). Ένα Delegate μπορεί να αναφέρεται μόνο σε συναρτήσεις που είναι συμβατές με το signature που περιγράφει.

Η έννοια των πραγματικών closures, ήρθε στην C# με την έκδοση 2.0, υπό τη μορφή των anonymous delegates (και κατεπέκταση στα lambdas), που σε γενικές γραμμές επιτρέπουν την προσπέλαση (read & write) μεταβλητών που δηλώνονται έξω από το scope αυτών. Χωρίς true closure support, είναι π.χ., η Java και ότι πιο κοντινό έχει σε αυτό το concept, είναι τα anonymous inners classes. Ένα simplistic closure support υπολογίζεται ότι θα υποστηριχθεί με την έλευση της νέας έκδοσης 7, το Σεπτέμβρη 2010.

Περιβάλλον ενός lambda

Στο παρακάτω μπλοκ κώδικα θα δούμε διαφορετικούς τύπους μεταβλητών, αναφορικά, με την θέση τους σε σχέση με το lambda.

Outer/free Variables: Οι μεταβλητές που βρίσκονται στο λεκτικό περιβάλλον του delegate (δηλαδή το scope τους περιλαμβάνει το delegate) θεωρούνται ώς πιθανές μεταβλητές που μπορούν να χρησιμοποιηθούν από αυτό. Ελεύθερες μεταβλητές λοιπόν για ένα lambda, είναι όσες δεν δηλώνονται μέσα στο scope του και όσες δεν δηλώνονται στις παραμέτρους τους (τότε θα λέγονταν bound variables). Μαθηματικώς, για παράδειγμα στον Fourier, για κάποιο χ τα a0, a1, a2, .., bn είναι ελεύθερες μεταβλητές (μπορούν να πάρουν τιμές από τον περιβάλλοντα χώρο) :

image

Όταν ένα free variable δεσμευτεί στο περιβάλλον του lambda, λέγεται ότι είναι ένα από τα Captured Variables, τα οποία είναι εξωτερικές μεταβλητές που χρησιμοποιούνται μέσα στο lambda. Ότι δεν είναι free variable, λέγεται bound variable, όπως για παράδειγμα η μεταβλητή Anonymous Local που ανήκει στις μεταβλητές που δηλώνονται και χρησιμοποιούνται μέσα στο lambda.

int outerVariable = 5; string capturedVariable = "captured"; if (DateTime.Now.Hour==23) {     int normalLocalVariable = DateTime.Now.Minute;           Console.WriteLine(normalLocalVariable); } ThreadStart x = delegate()     {         string anonLocal="local to anonymous method";            Console.WriteLine(capturedVariable + anonLocal);     }; x();

Όταν μέσα σε ένα lambda γίνεται αναφορά σε μία εξωτερική μεταβλητή, λέμε ότι η μεταβλητή αυτή, έχει δεσμευτεί (captured) και η ίδια ζει, όσο ζει και το lambda (γίνεται όπως λέμε, επέκταση του χρόνου ζωής της). Τι συμβαίνει στην πραγματικότητα όταν γίνεται capture; Ας το δούμε μέσα από ένα παράδειγμα.

Έστω ότι έχουμε μία συνάρτηση χωρίς capturing μεταβλητών:

static void F() {     D d = () => { Console.Writeline("No captured variables")}; }

όπου:

public delegate void D();

το delegate.

O compiler αντιμετωπίζει το delegate type instantiation (με το lambda που βλέπετε εκεί), κάνοντας allocation μία πραγματική ενδιάμεση μέθοδο που έχει κάνει generate.

static void F() {     D d = new D(generated_Method); } static void generated_Method() {     Console.Writeline("No captured variables") }

Έστω ότι το lambda κάνει capture μία τοπική μεταβλητή:

static void F() {     int y = 1;     D d = () => { Console.Writeline(y)}; }

H C# σε αυτό το σημείο, προσδίδει σημασιολογία closure και λέει: Επειδή το αντικείμενο που γίνεται reference από το d (η μέθοδος που κάνει Writeline το y, δηλαδή),  (μπορεί) να ζήσει περισσότερο από το scope της F (π.χ., αν επιστραφεί από τη συνάρτηση), κρατά τη μεταβλητή y (όχι απλά την τιμή της), για να την χρησιμοποιήσει, όταν το d γίνει invoke και εκτελεστεί. Πριν δείξουμε όμως την περίπτωση όπου το d ζει περισσότερο από το y (προς το παρόν δεν ζει) ας δείξουμε τι γίνεται σε αυτό το παράδειγμα. O compiler under the hood κάνει το εξής:

void F() {     Generated_Class_Keeping_Environment gen = new Generated_Class_Keeping_Environment();     gen.y = 1;     D d = new D(gen.generated_Method); } class Generated_Class_Keeping_Environment {     public int y;     public void generated_Method()     {         Console.Writeline(y);     } }

Ταξιδεύοντας λίγο περισσότερο στο flow, τι γίνεται αν έχω μεταβλητές σε διαφορετικά statement blocks, όπως στο παρακάτω παράδειγμα; Τότε ο compiler λειτουργεί αναλόγως. Στην παρακάτω περίπτωση, έχουμε αναφορά σε μία εξωτερική μεταβλητή (this.x)

class Top {     int x;     static void F()     {         int y = 1;         for (int i =0; i<10;i++)         {             int z = i*2;             D d = () => {Console.Writeline(x+y+z);};         }     } }

Στην παραπάνω μέθοδο F, το lambda κάνει capture μία μεταβλητή που δηλώνεται μία φορά στο this και δεν αλλάζει, μία τοπική μεταβλητή που δεν αλλάζει και μία τοπική μεταβλητή που αλλάζει συνεχώς, με κάθε iteration. O compiler θα κάνει generate δύο κλάσεις. Μία (Locals2) που θα περιέχει τη μέθοδο, την μεταβλητή Z, καθώς και αναφορά στην άλλη generated κλάση. Η δεύτερη generated κλάση (Locals1) θα έχει τη μεταβλητή y και ένα reference σε object τύπου Top οπότε η συνάρτηση F θα μετασχηματιστεί ως εξής:

void F () {     Locals1 locals1 = new Locals1();     locals1._this = this; //H Top     locals1.y = 1;     for (int i =0; i<10;i++)     {         Locals2 locals2 = new Locals2();         locals2.locals1 = locals1;         locals2.z = i*2;         D d = new D(locals2.method);     }    }

Όπως παρατηρούμε, οι μεταβλητές που έχουν γίνει capture, ζουν τώρα πια στο heap καθώς έχουν γίνει dynamic allocated μέσα σε κλάση.

Ας δούμε όμως, μία χρήση των anonymous delegates σαν κανονικά first order functions που είναι. Διαβάστε προσεκτικά τον παρακάτω κώδικα. Έστω ότι θέλω να ορίσω ThreadStart delegates. Φτιάχνω ένα array από δύο anonymous delegates μέσα στο for και αναθέτω σε κάθε ένα από τα δύο κελιά του array, από ένα anonymous delegate. Τα anonymous delegates έχουν κάνει capture δύο free variables, τις outside και inside (και allocate στο heap με τις compiler generated κλάσεις που είδαμε).

static void Main() {     ThreadStart[] delegates = new ThreadStart[2];     int outside = 0;                                               for (int i=0; i < 2; i++)         {             int inside = 0;                                                delegates[ i] = delegate                                        {                 Console.WriteLine ("({0},{1})",                                    outside, inside);                 outside++;                 inside++;             };         }         ThreadStart first = delegates[0];         ThreadStart second = delegates[1];         first();         first();         first();         second();         second();     } }

Σκεφτείτε ότι την ώρα που γίνεται η ανάθεση τιμής στο delegates[ i] γίνονται οι αναθέσεις, έχουν γίνει capture οι μεταβλητές και ο κώδικας εκτελείται παρακάτω. Γίνεται invoke το delegate της θέσης 0 και τυπώνει (0,0). Τη δεύτερη φορά και τα δύο αυξάνονται, όμοια και την τρίτη φορά και έχουμε (1,1) και (2,2). Τώρα εκτελείται το δεύτερο delegate. To outside είναι σε κατάσταση 3, οπότε με την αύξηση γίνεται 4, το inside όμως έχει ΞΑΝΑ-δηλωθεί (int inside = 0), οπότε το delegate έχει διαφορετικό instance της inside, αρχικοποιημένο στο 0. Οπότε η συνέχεια έπεται ως (3,0) και (4,1). Αν γράφατε τον παραπάνω κώδικα δείτε τι θα σας έδειχνε ο Resharper … ;) Πώς μπορώ να το διορθώσω αυτό, αν δεν το επιθυμώ (χωρίς να πατήσω την λάμπα αριστερά :P);

image

Γιατί είναι τόσο σημαντικά τα closure semantics;

Έστω ότι θέλουμε να φιλτράρουμε μία λίστα με βάση κάποια κριτήρια. Ας το υλοποιήσουμε απλά! Τι χρειαζόμαστε; Μία λίστα για να διαβάσουμε, έναν κανόνα που να δείχνει true αν κάνει pass το αντικείμενο και να θα το εισάγει σε μία νέα λίστα. Αφού διατρέξουμε όλη τη λίστα, θα έχουμε στα χέρια μας μία άλλη λίστα μόνο με όσα “πέρασαν”.

public static IList<T> FindAllItemsBasedOnPredicate<T>(IList<T> src, Predicate<T> predicate)     {         List<T> list= new List<T>();         foreach (T item in src)         {             if (predicate(item))              list.Add(item);                }         return list;     }


Όμορφα. Προχωράμε στο filtering. Έστω ότι θέλουμε να βρούμε όλους τους πελάτες ως 18 ετών.

Στη C# 3.0 (λόγω lambdas) μπορούμε να γράψουμε το εξής:

Predicate<Customer> ageRule = customer => customer.age <= 18; IList<Customer> youngCustomers= FindAllItemsBasedOnPredicate(CustomerList, ageRule );

Αν όμως δεν θέλουμε hard coded το 18 και χρειαζόμαστε να το ρυθμίζουμε μέσα από μία μεταβλητή; Έστω ότι δεν έχουμε closures. Τι κάνουμε; Το αλλάζουμε απλά με μία μεταβλητή; Και αν η μεταβλητή είναι static και αλλάξει; Και αν θέλουμε thread safety; Αν αναφερόμαστε σε Threading και θέλουμε να περάσουμε παραμέτρους στο thread μας; H C# έχει την δυνατότητα capturing μεταβλητής και όχι τιμής και αυτό είναι ένα από τα δυνατά της σημεία.

Some food for thought

Τι θα τυπώσει ο παρακάτω κώδικας χωρίς Copy-Paste-F5; ;););)

delegate void Func(); class Program {     static Func[] functionArray = new Func[10];     static void FillFunctionArray(int count)     {         for (int i = 0; i < count; i++)         {             functionArray[ i] = () =>             {                 Console.Write("{0} ", i);             };         }     }     static void Main(string[] args)     {         FillFunctionArray(functionArray.Length);         for (int i = 0; i < functionArray.Length; i++)         {             functionArray[ i]();         }         Console.ReadKey();                } }

Enjoy closures!!!

Για όσους ενδιαφέρονται να διαβάσουν μία ιδέα για το πώς μπορεί να επεκταθεί η IL για να υποστηρίζονται closures και άλλα όμορφα πράγματα σε επίπεδο IL, μπορούν να διαβάσουν μία σχετική δημοσίευση από MSR του Don Syme, δημιουργού της F#.

 

  1. D. Syme, “ILX: Extending the. NET Common IL for Functional Language Interoperability,” Electronic Notes in Theoretical Computer Science 59, no. 1 (2001): 53–72.
Posted: Τρίτη, 9 Μαρτίου 2010 12:13 πμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , , ,

Αντιγραφή αρχείου σε UNC Path προγραμματιστικά (ASP.NET or just code)

Η αντιγραφή αρχείου σε NAS, προγραμματιστικά (ή εν γένει σε κάποιο UNC location εκτός τοπικού μηχανήματος) είναι μία ανάλογη διαδικασία, όπως αν το κάνουμε μέσω του UI. Θα ανοίγαμε το Run, στην συνέχεια πληκτρολογούμε το unc path. Αν σε εκείνη την τοποθεσία έχουν τεθεί permissions για όλους, τότε το παράθυρο ανοίγει κατευθείαν. Σε αντίθετη περίπτωση θα πρέπει να εισαχθούν τα στοιχεία για να γίνει login ο χρήστης, είτε τοπικός, είτε domain (σε αυτήν την περίπτωση θα πρέπει να εισαχθεί και το domain με τον γνωστό τρόπο domain/username). Τι χρειάζεται όμως για να γίνει προγραμματιστικά η διεργασία και μάλιστα μέσα από κώδικα που εκτελείται στο περιβάλλον του IIS; Τότε αυτό που θέλουμε να κάνουμε λέγεται delegation και υπόκειται στο κεφάλαιο ASP.NET Delegation. Με βάση αυτό μπορεί η ASP.NET να προσπελάσει resources σε απομακρυσμένο σύστημα. Μία ειδική περίπτωση αυτού είναι το ASP.NET Impersonation με το οποίο το σύστημα είναι τοπικό. Mε αυτόν τον τρόπο οι ASP.NET εφαρμογές μπορούν να εκτελέσουν κώδικα κάτω από την ταυτότητα του χρήστη τον οποίο απαιτείται να χρησιμοποιήσουν για την πρόσβαση. Για να γίνει αυτό θα πρέπει τα δύο μηχανήματα να μελετηθούν ως προς το λειτουργικό τους, την ύπαρξη του δικαιώματος για delegation, κτλ. Επίσης θέλει προσοχή κατά το σχεδιασμό μιάς τέτοιας λύσης για λόγους ασφαλείας.

Έστω ότι έχω την περίπτωση που ο IIS τρέχει κάτω από το IUSR_machinename για ανώνυμη πρόσβαση. Αν γνωρίζω από πριν username και password τότε μπορώ ρυθμίζοντας το directive <processModel> να ελέγξω την παραπάνω διαδικασία (βλ. link για asp.net delegation). Στην περίπτωσή μου θέλω να ελέγχω την διαδικασία πλήρως χρησιμοποιώντας το LogonUser API. Θέλω λοιπόν να κάνω ένα συγκεκριμένο account login, όπως έδειξα και νωρίτερα όταν ήθελα να μεταφέρω ένα αρχείο από το ui. Δεν υπάρχει αντίστοιχη μέθοδος όμως (ως Login, αυτούσια), στο .ΝΕΤ και η μόνη μέθοδος που κάνει impersonate είναι η WindowsIdentity.Impersonate (IntPtr), από το System.Security.Principal. Τι είναι το IntPtr; Είναι ένα handle για τον χρήστη του συστήματος και ανακτάται από κλήση σε unmanaged κώδικα μέσω της LogonUser από το advapi32.dll.

Στον κώδικά μου ήθελα να μπορώ να τρέχω ένα File.Copy (ή οτιδήποτε) το οποίο θα εκτελείται με βάση συγκεκριμένα credentials.

Impersonation.Execute(myEntity.NasUser, myEntity.NasPassword, () => { //Copy File to NAS    File.Copy(sourceFile, Path.Combine(myEntity.UploadPath, Path.GetFileName(sourceFile)), true); });

 

#region DllImport         [DllImport("advapi32.dll", SetLastError = true)]         public static extern bool LogonUser(             string lpszUsername,             string lpszDomain,             string lpszPassword,             int dwLogonType,             int dwLogonProvider,             out IntPtr phToken             );         [DllImport("kernel32.dll", CharSet = CharSet.Auto)]         public extern static bool CloseHandle(IntPtr handle); #endregion

 

Έτσι η Execute που έγραψα, χοντρικά ήταν η εξής

public static void Execute(string userName, string domain, string password, Action action)         {             try             {                 bool bImpersonated = LogonUser(                     userName,                     domain,                     password,                     logon32LogonInteractive,                     logon32ProviderDefault,                     out tokenHandle);                 if (bImpersonated == false)                 {                     throw new Win32Exception(Marshal.GetLastWin32Error());                 }                 WindowsIdentity newId = new WindowsIdentity(tokenHandle);                 impersonatedUser = newId.Impersonate();                 action();             }             catch (Exception ex)             {                 throw ex;             }             finally             {                 if (impersonation != null)                     impersonation.Dispose();             }         }

Αξίζει να σημειώσουμε ότι στο τέλος θα πρέπει οπωσδήποτε να επαναφέρεται το process identity στο προηγούμενο (μέσω τη undo), γιατί θα υπάρξει πρόβλημα στις περαιτέρω ενέργειες

public void Dispose() {     // Stop impersonating the user.     if (impersonatedUser != null)         impersonatedUser.Undo();     // close handle     if (tokenHandle != IntPtr.Zero)         CloseHandle(tokenHandle); }

Τέλος η παραπάνω διεργασία χρειάζεται full trust (PermissionSetAttribute)

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

Παρουσίαση .NET και C# στο ΠΜΣ Πληροφοριακά Συστήματα ΟΠΑ – Τεχνολογία Λογισμικού

Παραθέτω το υλικό της σημερινής παρουσίασης για .NET, C# που είδαμε στο μάθημα Τεχνολογίας Λογισμικού (ευχαριστούμε τον καθηγητή Εμ. Γιακουμάκη που την πραγματοποιήσαμε επιτυχώς). Η παρουσίαση που διαδέχτηκε τη δική μου, από τον Μιχάλη Ζερβό, βρίσκεται εδώ. Και οι δύο, βρίσκονται στο e-class του μαθήματος.

Στην παρουσίασή μου, προσπάθησα να δώσω όσες περισσότερες ιδέες μπορούσα (στο σύντομο χρονικό διάστημα της μίας ώρας) αναδεικνύοντας την εκφραστικότητά της και τις δυνατότητες που έχει. Καλύφθηκαν αρκετά θέματα, ξεκινώντας από τις βασικές δομές και εξηγώντας στη συνέχεια μία πληθώρα από άλλες όπως, delegates, anonymous functions, anonymous types, iterators, lambdas, extension methods, LINQ καθώς επίσης τα βασικά από τη λειτουργία του ίδιου του περιβάλλοντος εκτέλεσης και της εσωτερικής οργάνωσης του .NET. H παρουσίαση, είχε στόχο να δώσει πολλές ιδέες και ερεθίσματα, καθώς δύσκολα μία έννοια μπορεί να διατυπωθεί μέσα από μερικά slides σε περιορισμένο χρόνο.

Τα Demos (WPF, F#) που παρουσίασα για τις ανάγκες περιήγησης στο Visual Studio βρίσκονται εδώ, καθώς και η παρουσίαση. Για να κατεβάσετε την Beta 2 του VS Studio 2010 Beta 2 θα ακολουθήσετε τον εν λόγω σύνδεσμο. Τέλος, για την σύντομη αναφορά στην F#, μπορείτε να δείτε μία επεξήγηση (στο ίδιο πρόγραμμα για τους πύργους του Ανόι) στο εξής blogpost.

 

Enjoy coding!!

Posted: Τρίτη, 12 Ιανουαρίου 2010 4:43 μμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , , ,

Εισαγωγή στα Expression Trees και στις δυναμικές μεθόδους (Visual Studio 2010)

Στο απόλυτο βιβλίο για compilers (καθιερωμένο ως Dragon Book εξαιτίας του concept art εξωφύλλου του στην πρώτη έκδοση) ως abtract syntax trees ή απλά syntax trees, ορίζονται οι ιεραρχικές συντακτικές δομές του πηγαίου κώδικα ενός προγράμματος. Όταν γράφουμε κώδικα, υπάρχει μία πάρα πολύ συγκεκριμένη διαδικασία η οποία μετατρέπει τις λέξεις και τα σύμβολα που γράφουμε, σε δομές που έχουν συγκεκριμένη σημασιολογία. Το πρώτο κομμάτι ενός μεταγλωττιστή ασχολείται με το “διάβασμα” του κώδικα που γράφουμε. Αυτή η διαδικασία αποτελείται από τρία στάδια. Στο πρώτο στάδιο υπάρχει ένας λεκτικός αναλυτής ο οποίος παίρνει ως είσοδο το αρχείο κειμένου που γράψαμε, και το σπάει σε τεμάχια ή αλλιώς tokens. Για παράδειγμα εμείς γράφουμε items + 1 και ο αναλυτής αναγνωρίζει ως token την λέξη item. Στην συνέχεια λαμβάνει χώρα η ανάλυση (parsing), που παίρνει τα tokens και σύμφωνα με τους κανόνες της γλώσσας, δημιουργεί συντακτικά δένδρα εκφράσεων. Στο τελευταίο βήμα λαμβάνονται οι εν λόγω δενδρικές δομές και παράγεται αυτό που λέμε ενδιάμεσος κώδικας. Υπάρχουν δύο ήδη συντακτικών δένδρων, τα αφηρημένα και τα μη (abstract και concrete). Σε ένα abstract syntax tree λοιπόν απεικονίζεται μία έκφραση, του οποίου ο εσωτερικός κόμβος είναι ένας operator και τα παιδιά είναι τα operands (τα οποία μπορούν με τη σειρά τους να είναι εκφράσεις). Η διαφορά με τα concrete syntax trees είναι ότι τα concrete έρχονται κατευθείαν από τον parser (αντικατοπτρίζοντας αποκλειστικά το συντακτικό της γλώσσας) ενώ τα abstract προσδίδουν με τη σειρά τους πρόσθετη πληροφορία (σχετικά με σημασιολογία π.χ.).

Πολύ όμορφα λοιπόν, γράφουμε λέξεις περιγράφοντας διαδικασίες οι οποίες μεταφράζονται σε εντολές. Στην γλώσσα C# τα expression trees αποτελούν έναν τύπο δεδομένων που αποθηκεύει κώδικα σε μορφή δεδομένων. Τα δεδομένα αποθηκεύονται υπό τη μορφή δένδρων, όπως ακριβώς τα δένδρα που περιέγραψα παραπάνω. Με το συγκεκριμένο τύπο δεδομένων μπορούμε να κάνουμε κάτι πάρα πολύ όμορφο και θεάρεστο :P: να αλλάξουμε ή να μετασχηματίσουμε κώδικα κατά το runtime execution, πριν τον εκτελέσουμε. Για παράδειγμα υπάρχει η δυνατότητα να μετατραπεί κώδικας C# (όπως κάνει η LINQ μέσω των query expressions) σε κώδικα που εκτελείται σε άλλο process, όπως είναι μία βάση δεδομένων.

Έστω ότι έχουμε την παρακάτω έκφραση lambda λοιπόν, η οποία μας περιγράφει μία συνάρτηση που παίρνει δύο ακεραίους ως παραμέτρους και επιστρέφει έναν άλλον ακέραιο.

Func<int, int, int> function = (a,b) => a + b;

Το παραπάνω μας βοηθάει να δηλώσουμε μία μεταβλητή συνάρτησης στην οποία αποθηκεύεται ένα lambda που υπολογίζει το άθροισμα δύο αριθμών (βλ. delegates και generic functions). Ακόμα και για κάποιον που δεν έχει κατανοήσει πολύ καλά τις δύο παραπάνω έννοιες, φαίνεται πολύ καθαρά από την παραπάνω γραμμή ότι κατά κάποιον τρόπο έχουμε αποθηκεύσει στη μεταβλητή function μία αναφορά προς εκτελέσιμο κώδικα. Τα expression trees δεν είναι εκτελέσιμος κώδικας, οπότε πώς μεταφράζουμε το παραπάνω expression σε expression tree;

Expression<Func<int, int, int>> expression = (a,b) => a + b;

Με την παραπάνω γραμμή κάναμε αυτό που θέλαμε! Δηλώσαμε μία μεταβλητή expression η οποία περιέχει σε μορφή δεδομένων το συντακτικό δένδρο που θέλαμε. Τι μπορούμε να κάνουμε με αυτό λοιπόν; Μπορούμε καταρχάς να πάρουμε το σώμα της συνάρτησης με το Property Body, τις παραμέτρους με το Property Parameter, τον τύπο του κόμβου που θέλουμε με το Property NodeType (η λίστα με τους τύπους expressions), ή απλά τον CLR τύπο με το Property Type. Για την καλύτερη κατανόηση της διαφοράς μεταξύ Node Type και Type παραθέτω το παράδειγμα από το msdn:

// NodeType is Constant; Type is System.Int32.  ConstantExpression constExpr1 = Expression.Constant(5);  // NodeType is Add; Type is System.Int32 BinaryExpression binExpr = Expression.Add(constExpr1, constExpr1)

Έστω ότι έχουμε το expression που περιγράψαμε παραπάνω: Τα “δεδομένα” μπορούν να ανακτηθούν ως εξής:

BinaryExpression body = (BinaryExpression)expression.Body; ParameterExpression left = (ParameterExpression)body.Left; ParameterExpression right = (ParameterExpression)body.Right;

Τι κάνουμε όμως αν έχουμε μία έκφραση σε μορφή δεδομένων και θέλουμε να την εκτελέσουμε; Ότι κάνουμε πάντα! Compile!

expression.Compile()(3, 5);

Μπορεί με πρώτη ματιά να φαίνεται ότι απλά προσθέσαμε overhead στην λειτουργικότητα των lambdas. Σκεφτείτε όμως μία χρήση που ήδη βλέπετε τριγύρω σας. Πόσα διαφορετικά είδη LINQ εκτός από LINQ to objects υπάρχουν; Πώς τα queries που γράφουμε, μπορούν να εκτελεστούν σε διαφορετικά processes που τρέχουν κάποια εφαρμογή; Μέχρι τώρα λοιπόν, αυτή είναι η αλήθεια για την κύρια χρήση των expression trees. Χρησιμοποιούνται σε LINQ providers καθώς επίσης και σε μετατροπές expression trees και compilation. Στο Visual Studio 2010 τα Expression Trees έχουν επεκταθεί και κάποιος προγραμματιστής μπορεί τώρα πια με τη χρήση τους, να παράγει και δυναμικές μεθόδους (χωρίς MSIL). Αυτή η νέα δυνατότητα δεν μπορεί να πραγματοποιηθεί με lambda expressions αλλά μόνο με το νεό Expression Trees APΙ (διαθέσιμο στο Visual Studio 2010 έχοντας διατηρήσει και το υπάρχον functionality) που το επιτρέπει αυτό. Ας ξεκινήσουμε με την Expression.Block που συμβάλει στις νέες δυνατότητες των Expression Trees και επιτρέπει σειριακή εκτέλεση expressions. Το παράδειγμα πηγάζει από εδώ:

// Δημιουργώ ένα expression που θα κρατήσει την παράμετρο int arg ParameterExpression param = Expression.Parameter(typeof(int), "arg"); // Δημιουργώ ένα expression που τυπώνει ένα string MethodCallExpression firstMethodCall = Expression.Call(     typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) }),     Expression.Constant("Print arg:")); // Δημιουργώ ένα expression που τυπώνει την τιμή της παραμέτρου param (int arg) MethodCallExpression secondMethodCall = Expression.Call(     typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }),     param); // Βάζω σε σειρά εκτέλεσης τα δύο expressions BlockExpression block = Expression.Block(firstMethodCall, secondMethodCall); // Compiling ένα lambda κλείνοντας τον τύπο μεθόδου // ως μία μέθοδο που παίρνει μία παράμετρο το οποίο lambda // γίνεται construct με το προηγούμενο block ένα πίνακα // με τις παραμέτρους και την αποτίμηση τους στην παράμετρο του lambda Expression.Lambda<Action<int>>(block,new ParameterExpression[] { param }).Compile()(10); Console.ReadKey();

Το αποτέλεσμα έχει ως εξής:

image

Στο ίδιο blog post μπορούμε να βρούμε ένα πιο δύσκολο παράδειγμα, παρουσιάζοντας πώς μπορούμε δυναμικά να φτιάξουμε μία μέθοδο που να παίρνει ως όρισμα έναν αριθμό (Expression.Parameter), να δηλώσουμε μία μεταβλητή (Expression.Assign) και ένα επαναληπτικό block (Expression.Loop), φτιάχνοντας την Factorial.

Posted: Σάββατο, 28 Νοεμβρίου 2009 4:29 πμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: ,

Ασφαλής χρήση FileSystemWatcher

Υπάρχουν σενάρια που χρειάζεται να ξέρετε πότε ένα καινούριο αρχείο δημιουργείται σε έναν κατάλογο, ή πότε γίνεται rename, ή πότε αλλάζει το Last Changed timestamp για να κάνετε διάφορες εργασίες. Έστω το σενάριο, ότι έχετε ένα εξωτερικό εργαλείο που κάνει κάποια λειτουργία μετατροπής αρχείων. Για να μπορείτε να αντιληφθείτε πότε το αρχείο γράφτηκε στο folder που παρατηρείτε και να αντιγράψετε το αρχείο αυτό κάπου αλλού, ή να το επεξεργαστείτε, μπορείτε να χρησιμοποιήσετε το FileSystemWatcher του .NET. Παρόλα αυτά υπάρχει ένα θέμα εδώ. Ο FileSystemWatcher είναι καθαρά για reporting σκοπούς. Αν εσείς χρειάζεστε το αρχείο αυτούσιο, θα πρέπει να γνωρίζετε για το πότε το αρχείο είναι επιπλέον ελεύθερο από οποιοδήποτε άλλο process (π.χ., εκείνου που το αντιγράφει στο folder που κοιτάται). Ένα απλός και γρήγορος τρόπος, για να αποφύγουμε το IOException σε τέτοια περίπτωση είναι ο παρακάτω:

Δημιουργούμε μία συνάρτηση που ελέγχει για το αν το αρχείο είναι κλειδωμένο.

private static bool CheckFileLock(string filename)
{
    try
    {
        using (FileStream inputStream = File.Open(filename, 
                                    FileMode.Open,
                                    FileAccess.Read,
                                    FileShare.None))
        {
            return true;
        }
    }
    catch (IOException)
    {
      //Log maybe
        return false;
    }
}

Στη συνέχεια ο Watcher σας για να δουλέψει θα χρειαστεί τον event handler για το event Created. Ο Handler αυτός θα πρέπει να τσεκάρει ανά τακτά χρονικά διαστήματα (απλή λύση) για τη διαθεσιμότητα του αρχείου και that’s it.

private void onFileCreated(object sender, FileSystemEventArgs e)
{
    DateTime receivedTime = DateTime.Now;
    int delay = ...
    while (true)
    {
        if (CheckFileLock(e.FullPath))
        {
            //Do whatever you want safely
            break;
        }
        TimeSpan elapsedTime = DateTime.Now - receivedTime;
        if (elapsedTime.TotalMinutes > max)
        {
            //Logging definitely 
            break;
        }
        Thread.Sleep(delay);
    }
}

Στην καθυστέρηση που εισάγετε μπορεί κάποιος να χρησιμοποιήσει όποια στρατηγική θέλει, από σταθερό αριθμό ή κάποιο είδους αυξανόμενου.

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

Windows Sensor and Location platform

Τα Windows 7 περιέχουν ενσωματωμένη (native) υποστήριξη για διαχείριση αισθητήρων μαζί με μία προγραμματιστική πλατφόρμα που την συνοδεύει. Αφορά location sensors, temperature, accelerometer, ambient light, moisture, camera, RFID και πολλούς ακόμα τύπους αισθητήρων, που κοινώς φέρνουν τον enterprise developer πιο κοντά στις εφαρμογές διάχυτου υπολογισμού, από το να κάνουν μία απλή εφαρμογή mashup που θα χρησιμοποιεί δεδομένα από gps μέχρι μία sophisticated πλατφόρμα data fusion. H πλατφόρμα ονομάζεται Sensor and Location Platform και περιλαμβάνει τόσο managed API (C#) όσο και native (C++ με δικό μας memory management). Στην ουσία αποτελείται από 4 πράγματα όλη η πλατφόρμα:

  • To DDI (device driver interface) παρέχει μέσω των Windows έναν πρότυπο τρόπο για να συνδέονται οι αισθητήρες στο σύστημα και να μπορούν να παρέχουν σε αυτό πληροφορίες
  • Το Windows Sensor API για την διαχείριση των δεδομένων
  • To Windows Location API (χρησιμοποιεί το 2ο layer) για την διαχείριση χωρικών δεδομένων
  • Το Location and Other Sensors Control Panel για διαχειριστικά θέματα μέσα, συσχετίζοντας χρήστες με υποδομές αισθητήρων.

Και το software stack φαίνεται παρακάτω:

clip_image001[4]

 

Μία πρώτη γεύση μπορείτε να δείτε στο Windows 7 training kit που έχει μία απλή WPF εφαρμογή, που αλλάζει τη φωτεινότητα της προβαλλόμενης εικόνας με δεδομένα από sensors έντασης φωτός, με τη μισή μαγεία στο XAML (old news) και την άλλη μισή στο SensorHelper.cs αρχείο (Windows 7 platform). Στο View Model του sensor δε (στο SensorViewModel.cs) θα γλείφετε τα δάχτυλά σας, με κάτι δηλώσεις του τύπου που βλέπετε παρακάτω (constructor). Με την πρώτη ματιά βλέπετε ότι τα πράγματα έχουν προσαρμοστεί ακριβώς στο πεδίο των εφαρμογών επίγνωσης πλαισίου με όλο τον boilerplate κώδικα να έχει εισαχθεί μέσα στη πλατφόρμα με ενσωματωμένους τύπους δεδομένων, methods για data retrieval κτλ κτλ.

public SensorViewModel()
{
  _sensor = new SensorHelper<AmbientLightSensor, AmbientLightSensor.LuminousIntensity>();
  _sensor.Initialize();
  _acceleromaterSensor = new SensorHelper<Accelerometer3D, Accelerometer3D.Acceleration3D>();
  _acceleromaterSensor.Initialize();
}

Διαβάστε το αντίστοιχο συνοπτικό docx του lab να δείτε τι εννοώ!!! Επίσης μπορείτε να ανατρέξετε για πληροφορίες της πλατφόρμας στα παρακάτω:

 

Videos από channel 9:

http://channel9.msdn.com/tags/Sensor+and+Location+Platform/

 

Πηγές:

http://www.microsoft.com/whdc/device/sensors/default.mspx

Sensor Programming Guide (MSDN Library)

Location Programming Guide (MSDN Library)

Sensor and Location Platform Developer Resources

Posted: Τρίτη, 25 Αυγούστου 2009 12:58 πμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , ,

NScanner: Port Scanner / Port Sweeper

 

Πρόσφατα ολοκληρώσαμε έναν ανιχνευτή κατάστασης θυρών, ο οποίος να παρουσιάζει διπλή λειτουργικότητα, να είναι και Port scanner αλλά και Port sweeper. Ο Port Scanner θα δέχεται ως είσοδο μια δ/νση ΙΡ (ή hostname) και range από πόρτες (TCP/UDP) που θα θέλαμε να ανιχνεύσουμε. Ο Port Sweeper θα δέχεται ως είσοδο μια πόρτα (TCP/UDP) που θα θέλαμε να ανιχνεύσουμε και λίστα από δ/νσεις ΙΡ (ή DNS name). Η εφαρμογή υλοποιεί TCP, UDP και SYN scan με επιλογές που να τα παραμετροποιούν.

Η εφαρμογή αποτελείται από το βασικό παράθυρο στο οποίο μπορεί ο χρήστης να κάνει τις κατάλληλες παραμετροποιήσεις και να εισάγει τα στοιχεία που αυτός θέλει. Τα αποτελέσματα εκτυπώνονται στα δεξιά του παραθύρου ενώ στο κάτω μέρος υπάρχει και ένα progress bar το οποίο ενημερώνει τον χρήστη για το ποσοστό του ελέγχου που έχει απομείνει. Λοιπές λειτουργίες όπως logging, εκτύπωση ονομάτων θυρών που αναφέρονται κτλ, περιγράφονται παρακάτω.

TCP Scan

Στο TCP Scan αξιοποιείται η αναλυτική διαδικασία σύνδεσης με Berkeley sockets σε .NET (Socket Class (System.Net.Sockets)). O σκοπός που γίνεται αυτό είναι γιατί έτσι μπορούμε να έχουμε πλήρη έλεγχο κατά την δημιουργία των sockets. H γενική ιδέα είναι ότι, πραγματοποιείται ολόκληρη η διαδικασία του 3-way handshake, κατά την εγκαθίδρυση της σύνδεσης, το οποίο αποτυγχάνει κάτω από συγκεκριμένες συνθήκες, που αποκαλύπτουν την κατάσταση της θύρας. Στον παρόν port scanner που περιγράφεται, ανιχνεύονται θύρες σε τρεις διακριτές καταστάσεις σύμφωνα με την απόκριση τους: ανοικτές, κλειστές φιλτραρισμένες. Οι ανοικτές θύρες (πραγματοποιούν σύνδεση) αποκαλύπτουν ότι η port δεν υπόκειται σε έλεγχο firewall σχετικά με την κατάστασή της και επιτρέπει τις συνδέσεις (ανοικτή και ταυτόχρονα κάποια υπηρεσία την χρησιμοποιεί για να εξυπηρετεί προγράμματα «πελάτες»). Η δεύτερη κατηγορία (κλειστές) αναφέρεται στην κατάσταση όπου μία θύρα αποκρίνεται ότι δεν δέχεται συνδέσεις. Τέλος η κατηγορία στην οποία υπόκεινται οι φιλτραρισμένες θύρες, είναι όταν δεν δεχόμαστε απόκριση στην αποστολή αίτησης προς εκείνες.

UDP Scan

Στο UDP Scan χρησιμοποιείται μία παρόμοια μέθοδος (προγραμματιστικά), ορίζοντας μία περίοδο timeout που επηρεάζει το αποτέλεσμα. Το πρωτόκολλο UDP δεν έχει ανάγκη για SYN, FIN ή κάποια άλλη διαδικασία εγκαθίδρυσης σύνδεσης. Με το πρωτόκολλο UDP τα πακέτα αποστέλλονται και λαμβάνονται χωρίς κάποια προειδοποίηση, πράγμα που απλοποιεί την διαδικασία του port scanning (με περισσότερο αβέβαια αποτελέσματα βέβαια). Οι καταστάσεις που εντοπίζουμε είναι τρεις: Closed, Open/Filtered, Open.

SYN Scan

Στην μέθοδο SYN Scan συλλέγεται πληροφορία για την εμπλεκόμενη θύρα πριν ολοκληρωθεί η διαδικασία της εγκαθίδρυσης σύνδεσης. Όταν μία θύρα αναγνωρίζεται ως ανοικτή το TCP Handshake τερματίζεται με σήμα RST, πριν ακόμα ολοκληρωθεί η διαδικασία. Αυτή η τεχνική συνήθως αναφέρεται και ως half open scan. Σε περίπτωση που η εφαρμογή λάβει σήμα RST μετά από σήμα SYN, σημαίνει ότι η θύρα είναι κλειστή.

Για την παρούσα μέθοδο χρειάστηκε η κατασκευή των πακέτων με αναλυτικό τρόπο όπως επίσης και οι αποκρίσεις με σήματα RST σε περίπτωση που το άλλο άκρο αποκριθεί θετικά (SYN & ACK). Η αναγνώριση των στόχων, των gateway και πολλών στοιχείων που χρειάζονται για την κατασκευή των πακέτων, έχουν γίνει προγραμματιστικά στην συνάρτηση Connect που υπάρχει στην κλάση SynConnectCall.

Τα πλεονεκτήματα της συγκεκριμένης μεθόδου είναι ότι το γεγονός ποτέ δεν καταγράφεται από την εφαρμογή του στόχου, γιατί απλά δεν πραγματοποιείται σύνδεση TCP. Αυτό είναι ένα κατά πολύ πιο «αθόρυβο» scan από άλλα, τα οποία παρουσιάζουν μικρότερη ορατότητα από πλευράς του στόχου. Παρέχει πληροφορίες όπως open, closed και filtered θύρες.

Ανάπτυξη σε C#

Η εφαρμογή μας υλοποιήθηκε σε περιβάλλον .NET 3.5 και σε γλώσσα προγραμματισμού C Sharp (C#), ενώ για το γραφικό περιβάλλον της εφαρμογής χρησιμοποιήσαμε Winforms. Για την υλοποίηση της λειτουργικότητας SYN Scan χρησιμοποιήθηκε η βιβλιοθήκη libpcap[1]. Επίσης για της ανάγκες υλοποίησης σε C# χρησιμοποιήθηκε η υπό ανάπτυξη βιβλιοθήκη που ενθυλακώνει τις native κλήσης συστήματος της βιβλιοθήκης της libpcap ώστε να είναι δυνατή η χρησιμοποίησή της μέσα από περιβάλλον .NET. H εν λόγω βιβλιοθήκη ονομάζεται SharpPcap και διανείμετε δωρεάν, μέσα από το SourceForge.ΝΕΤ[2]. Αξίζει να σημειωθεί ότι έγιναν αλλαγές στον κώδικα του wrapper, όπως και διορθώθηκαν λάθη τα οποία αναφέρθηκαν στην ομάδα ανάπτυξης του, για να ληφθούν υπόψη στη περαιτέρω διαδικασία ανάπτυξής του [3] [4].

Οι μέθοδοι που υλοποιούνται ενθυλακώνονται στις αντίστοιχες κλάσεις <Protocol>ConnectCall (π.χ., το tcp scan υλοποιείται στην TcpConnectCall).

Screenshots

Port Sweeping
image

UDP Port Scanning
image

 

H εφαρμογή αναπτύχθηκε στα πλαίσια εργασίας για το μάθημα “Ασφάλεια υπολογιστών και Δικτύων”, Οικονομικό Πανεπιστήμιο Αθηνών, ΠΜΣ Πληροφοριακά Συστήματα 2008-2009, από τους Άγγελος Μπιμπούδη (εμένα) και Αναστάσιο Νεραντζίνη και καταχωρήθηκε στο codeplex από όπου θα εξελιχθεί/βελτιωθεί. Η άδεια χρήσης είναι GNU Library General Public License (LGPL).

http://nscanner.codeplex.com/

Posted: Τρίτη, 2 Ιουνίου 2009 9:23 μμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , , ,