Thursday, March 29, 2007

Asynchronous Data templates, data providers and Data Binding

DataBinding, templates and providers and really cool things in WPF, but have you tried to load something really heavy? How time it's take to load 20 images? A couple of seconds? And what about if each image is 3000X3000 px? Huh, your HMI thread will be locked until all those images will be downloaded and displayed. In this post, I'll explain how to build asynchronous XMLDataProvider, that load RSS from Flickr, asynchronous DataTemplate, that loaded image by image without locking your application and asynchronous Binding, that provides an ability to separate images and not to load all of them, if they not visible. So, let's start.

First of all we have to retrieve Flickr RSS feed. So, we'll create XmlDataProvider for this

<XmlDataProvider  
                 Source="http://api.flickr.com/services/feeds/photos_public.gne?tags=wallpaper&amp;format=rss_200" 
                 XPath="/rss/channel/item" x:Key="src"/>

This the first and really easy part. Nice, now we'll create data template and the host for it



<DataTemplate x:Key="img">
      <Image Source="{Binding XPath=media:content/@url}" Stretch="Uniform"/>
</DataTemplate>
 


<ListBox Grid.Row="1" ItemsSource="{Binding Source={StaticResource src}}"
            ItemTemplate="{StaticResource img}"/>

Fine, run it to see nothing. Why? because of XmlNamespace media, found in RSS 2.0. We should tell XmlDataProvider what's the namespace, so we have to add Namespace manager to point to http://search.yahoo.com/mrss/ URI, that actually describes media namespace. To do it, all you have to do it to add XmlNamespaceMapping to your resources and point XmlDataProvider to see it.


 



<XmlNamespaceMappingCollection x:Key="map">
      <XmlNamespaceMapping Uri="http://search.yahoo.com/mrss/" Prefix="media"/>
</XmlNamespaceMappingCollection>
 
<XmlDataProvider XmlNamespaceManager="{StaticResource map}" 
                     Source="http://api.flickr.com/services/feeds/photos_public.gne?tags=wallpaper&amp;format=rss_200" 
                     XPath="/rss/channel/item" x:Key="src"/>

So far so good, but now, when you'll run your application, it become frozen for 15 minutes, until all images will be loaded, then it force your CPU to 100% for 2 minutes to render all those images. Hm, well, you have really fast internet and your computer has Four Quad Core CPU. In this case, your application freeze for only three minutes. Really bad, isn't it?


So what to do? Let try first of all convert our data source to asynchronous. In order to do it, we'll set IsAsynchronous property to"True". Additional fix we'll add is to force the data provider to work, even before, we'll need it. Set IsInitialLoadEnabled="True". Compile.


Now it's better. We just saved the interface from freezing for 10 seconds (while RSS loaded). Still bad, really bad! The actual problem is not in DataProvider, but in all those urls and large images in it. Let make our DataBinding to work asynchronous. Let's make it load images one by one.


We'll add another property to the binding expression of our hosting control. And now it'll looks like this


 



<ListBox ItemsSource="{Binding IsAsync=True, Source={StaticResource src}}"
             ItemTemplate="{StaticResource img}"/>

Very well. Now our UI is released and all images loaded one-by-one without holding STAThread, which actually run it.


The next challenge is to force an application not to load all those images, which are not visible and see how many images already downloaded. For this, we'll add eventhandler Loaded event of image in the template. Add read only Dependency Property to your window and bind it to textblock to see.



int loadedTotalCounter = 0;
        void onImgLoaded(object sender, RoutedEventArgs e)
        {

SetValue(ImagesTotalPropertyKey, ++loadedTotalCounter);
        }


public static readonly DependencyPropertyKey ImagesTotalPropertyKey = DependencyProperty.RegisterReadOnly("ImagesTotal",

    typeof(int),

    typeof(Window1),

    new PropertyMetadata(0));

 

public static readonly DependencyProperty ImagesTotalProperty = ImagesTotalPropertyKey.DependencyProperty;

 

public int ImagesTotal

{

    get { return (int)GetValue(ImagesTotalProperty); }

}

Wow, if we'll put our ListView into grid, all invisible items will not being loaded, until we'll reveal them. Nice feature. Really nice, but why our counter of loaded images grows, even if the images are invisible?


The reason is simple, ImageSource is asynchronous property, so when we even got the source, this does not means, that the image is really loaded, so how to figure the actually number of loaded images. Let's dive deeper into ImageSource.


The object behind the ImageSource is BitmapFrame and it's (as we already tell) asynchronous object, so it has Download-related events such as DownloadCompleted, DownloadProgress and DownloadFailed. Let's put another readonly DP to our window, to cound actually downloaded frames


 



public static readonly DependencyPropertyKey ImagesLoadedPropertyKey = DependencyProperty.RegisterReadOnly("ImagesLoaded",
            typeof(int),
            typeof(Window1),
            new PropertyMetadata(0));
 
        public static readonly DependencyProperty ImagesLoadedProperty = ImagesLoadedPropertyKey.DependencyProperty;
 
        public int ImagesLoaded
        {
            get { return (int)GetValue(ImagesLoadedProperty); }
        }
 
        public static readonly DependencyPropertyKey ImagesTotalPropertyKey = DependencyProperty.RegisterReadOnly("ImagesTotal",
            typeof(int),
            typeof(Window1),
            new PropertyMetadata(0));

Now, let's get actual source and subscribe it to counter


 


void onDownloadCompleted(object sender, EventArgs e)
        {
            SetValue(ImagesLoadedPropertyKey, ++loadedCounter);
        }
 
 
 
        int loadedCounter = 0;
        int loadedTotalCounter = 0;
        void onImgLoaded(object sender, RoutedEventArgs e)
        {
            Image img = sender as Image;
            if (img != null)
            {
                BitmapFrame bimage = img.Source as BitmapFrame;
                if (bimage != null)
                {
                    bimage.DownloadCompleted += new EventHandler(onDownloadCompleted);
                }
            }
            SetValue(ImagesTotalPropertyKey, ++loadedTotalCounter);

        }

That's works great, but so some reason, DownloadCompleted event not always being fired. Recently, when the framework "skips" it, the image appears too fast. Is it bug? Not really. The real source for image is not network, but local cache, so not always BitmapFrame downloaded. And if it is not, why to fire event. Add handler for this, set break point and see.


 


void onDownloadCompleted(object sender, EventArgs e)
        {
            SetValue(ImagesLoadedPropertyKey, ++loadedCounter);
        }
 
 
 
        int loadedCounter = 0;
        int loadedTotalCounter = 0;
        void onImgLoaded(object sender, RoutedEventArgs e)
        {
            Image img = sender as Image;
            if (img != null)
            {
                BitmapFrame bimage = img.Source as BitmapFrame;
                if (bimage != null)
                {
                    //why this?
                    //Actually, the images are loaded from cache, so we have to check if it's downloading before, we'll want it to complete an action :)
                    if (bimage.IsDownloading)
                    {
                        bimage.DownloadCompleted += new EventHandler(onDownloadCompleted);
                    }
                    else
                    {
                        SetValue(ImagesLoadedPropertyKey, ++loadedCounter);
                    }
                }
            }
            SetValue(ImagesTotalPropertyKey, ++loadedTotalCounter);

        }

Dunno. Now we have everything working asynchronous way and our UI is free for action. Does WPF architecture is really genius to think about all those cases?


Source code for this article

textbox for high-contrast very small text

 The most common question about text capabilities in WPF is about small fonts. By default all texts rendered with presentation layer are anti-aliased,  wich means, that we will get great text experience with large fonts, and blurry texts with small and extra small fonts. 

As you, probably know, the common usage of big fonts is in entertainment-related application, however most of business-related applications using small fonts. There is impossible to solve "blurry" problem with WPF texts, so today, we'll solve it with WPF geometry. Today, we'll create custom textblock, that holds texts, which visible and readable on any background, even with very small fonts. 

 

 So, let the show start. First of all, we'll create new control, derived from FrameworkElement. We'll add a couple of dependency properties for Text, Font, FontSize, fore and background color. But, were the trick, you'll ask... Instead of drawing text, onRender event, we'll draw geometry of the text and stroke it with contrast color.

 

protected override void OnRender(DrawingContext drawingContext)
       {
           drawingContext.DrawGeometry(Fill, m_pen, m_textg);
       }

The next question is - where to get the geometry of the text? FormattedText class exposes method named BuildGeomerty, that converts text into geometrical representation. So, in order to build a text and it's geometry, we'll build internal method, for text generation, based on control's DPs


 



private void GenerateText()
        {
            if (Font == null)
                Font = new FontFamily("Arial");
 
            FormattedText fText = new FormattedText(
               Text,
               CultureInfo.CurrentCulture,
               FlowDirection.LeftToRight,
               new Typeface(
                   Font,
                   FontStyles.Normal,
                   FontWeights.Heavy,
                   FontStretches.Normal),
               FontSize,
               Brushes.Black
               );
 
            m_textg = fText.BuildGeometry(new Point(0, 0)).GetAsFrozen() as Geometry;
        }

So now, after compilation, we'll get the text stroked with contrast pen, but it will be anti-aliased. What to do? Framework, can not cancel anti-aliasing for text, but it can do it for geometry. In order to do it, just call the framework to cancel it.



RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);


There are a couple of other tips you'll have to do. One is snap your graphics to device pixels. Why to do it? Read MSDN :)



this.SnapsToDevicePixels = true;


Additional tip is set linejoin to Round and MeterLimit to 1 for line "pike" prevention. One other trick is to freeze the pen after generation, because, you wont going to change it while rendering, so save the system resources.


 



private static void OnTextInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            m_pen = new Pen(((HiResTextBlock)d).Stroke, ((HiResTextBlock)d).StrokeThickness);
            m_pen.LineJoin = PenLineJoin.Round;
            m_pen.MiterLimit = 1;
            m_pen = m_pen.GetAsFrozen() as Pen;
            ((HiResTextBlock)d).GenerateText();
        }

Now, tell the framework to call all those methods by doing this and you're done


 



public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text",
            typeof(string),
            typeof(HiResTextBlock),
            new FrameworkPropertyMetadata(
                 "",
                 FrameworkPropertyMetadataOptions.AffectsRender,
                 new PropertyChangedCallback(OnTextInvalidated),
                 null
                 )
            );

Now, let's see what we get. Pretty nice isn't it? 



 Source code for this article

Creating datatemplates from code

It's really easy to create data templates from XAML, but today, one of my clients asks me to do it from code. The other request was to handle events of element form inside the template. So let's do it together

First of all we have to create DataProvider as we usual do in xaml.

 

XmlDataProvider data = new XmlDataProvider();
           data.Document = new System.Xml.XmlDocument();
           data.Document.InnerXml = getXMLstring();
           data.XPath = "/root/node";

Next step is to create binding expression for data template usage


 



Binding itemsBinding = new Binding();
            itemsBinding.XPath = "val";

So far, so good. Now comes the trick. In order to use DataTemplate class, we have to create Visual Tree of elements, being used in the template. In order to do it, first we should create FrameworkElementFactory and set all necessary handlers and properties there. The factory, is actually, kind of meta element,  derived from FrameworkElement and makes us able to markup anything, we have to have within the instance of future element. So let's do it


 



FrameworkElementFactory comboFactory = new FrameworkElementFactory(typeof(ComboBox));
            comboFactory.Name = "myComboFactory";
            comboFactory.SetBinding(ComboBox.ItemsSourceProperty, itemsBinding);
            comboFactory.AddHandler(ComboBox.SelectionChangedEvent, new SelectionChangedEventHandler(popup));

As you can see, I'm setting Binding and add event handler to FrameworkElementFactory in order them to appears in real element that will be generated by the factory.


The next step is rather simple. We'll create DataTemplate and set it's VisualTree property to newly created factory.


 



DataTemplate itemsTemplate = new DataTemplate();
           itemsTemplate.VisualTree = comboFactory;

Another binding, setting ItemTemplate property and we done.


 



Binding nodeBinding = new Binding();
            nodeBinding.Source = data;
 
            ListView lv = new ListView();
            lv.ItemTemplate = itemsTemplate;
            lv.SetBinding(ListView.ItemsSourceProperty, nodeBinding);
 
            myGrid.Children.Add(lv);

It's rather straight-forward to do it, but still, it's much simpler to create templates from XAML, isn't it? :)


Source code for this article

Wednesday, March 28, 2007

Camtasia 4.01 for Vista is shipping

Finally, TechSmith released the final new version of their Camtasia Studio. Now, it's Microsoft Windows Vista compatible. I will now explain about this product, but just in case, this is the best tool for screen capturing ever. All those who had Camtasia used, know it.

Currently, I'm using their beta version, downloaded a couple of months ago. It's running fine on Vista, but still there are some issues.

For all those who have not purchased this tool, do it - it worth each penny of $299 individual license.

Grid animation

One of most common questions, I meet with my customers is how to animate grid size. Actually, there is no problem to animate doubles, ints, even sizes, but how to animate GridLength, which actually used to measuring the sizes in grid?

In order to do it, we'll create our own animation, named GridLengthAnimation and use it within our grid. So, let's start.

First of all we have to inherit base animation timeline in order to get the functionality we want.

 

public abstract class GridLengthAnimationBase : AnimationTimeline
    {
        protected GridLengthAnimationBase() { }
        public override sealed Type TargetPropertyType
        {
            get { return typeof(GridLength); }
        }
        public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
        {
            if (defaultOriginValue is GridLength == false)
                throw new ArgumentException("Parameter must be a GridLength", "defaultOriginValue");
            if (defaultDestinationValue is GridLength == false)
                throw new ArgumentException("Parameter must be a GridLength", "defaultDestinationValue");
            return GetCurrentValueCore((GridLength)defaultOriginValue, (GridLength)defaultDestinationValue, animationClock);
        }
        public abstract GridLength GetCurrentValueCore(GridLength defaultOriginValue, GridLength defaultDestinationValue, AnimationClock animationClock);
    }

Next step is to create actual animation. It's pretty simple, when we already have base type. We have to create some Dependency Properties used within timeline, such as By, From, To etc. and treat of some possible errors (we do not want our animation to be thrown :) )


 



public class GridLengthAnimation : GridLengthAnimationBase
{
    public static readonly DependencyProperty ByProperty = DependencyProperty.Register(
     "By",
     typeof(double?),
     typeof(GridLengthAnimation),
     new PropertyMetadata(null));
    public static readonly DependencyProperty FromProperty = DependencyProperty.Register(
     "From",
     typeof(double?),
     typeof(GridLengthAnimation),
     new PropertyMetadata(null));
    public static readonly DependencyProperty ToProperty = DependencyProperty.Register(
     "To",
     typeof(double?),
     typeof(GridLengthAnimation),
     new PropertyMetadata(null));
    public double? By
    {
        get { return (double?)this.GetValue(ByProperty); }
        set { this.SetValue(ByProperty, value); }
    }
    public double? From
    {
        get { return (double?)this.GetValue(FromProperty); }
        set { this.SetValue(FromProperty, value); }
    }
    public double? To
    {
        get { return (double?)this.GetValue(ToProperty); }
        set { this.SetValue(ToProperty, value); }
    }
    protected override Freezable CreateInstanceCore()
    {
        return new GridLengthAnimation();
    }
    public override GridLength GetCurrentValueCore(GridLength defaultOriginValue, GridLength defaultDestinationValue, AnimationClock animationClock)
    {
        if (From == null)
            throw new Exception("From must be specified in a GridLengthAnimation");
        double a_to;
        if (To != null)
            a_to = To.Value;
        else if (By != null)
            a_to = From.Value + By.Value;
        else
            throw new Exception("Either To or By must be specified in a GridLengthAnimation");
        return new GridLength(From.Value + ((a_to - From.Value) * animationClock.CurrentProgress.Value));
    }
}

That's all, folks. All we have to do now is to use our new animation type with our grid as we know to use any other type of animation. It's looks like this


 


<BeginStoryboard>
              <Storyboard>
                <ParallelTimeline>
                  <local:GridLengthAnimation
                    Storyboard.TargetName="l"
                    Storyboard.TargetProperty="(ColumnDefinition.Width)"
                    From="30"
                    To="300"
                    AutoReverse="False"
                    Duration="0:0:1"/>
                </ParallelTimeline>
              </Storyboard>
            </BeginStoryboard>

Hurrah, from now we know to build any type of animations and actually do not need Microsoft to provide us with premade one. Do't it looks like working with converters? The answer is "yes".


Source code for this article

Tuesday, March 20, 2007

Adding Web References to your VS project by code

Today one of my clients asked me to find a way to add web references to existing VS project without using Visual Studio wizard, to do it by code. I built VS add-ins before, so I know what Studio can do, but the question is if I can replace existing system dialogs, without writing a lot of code (I'm really lazy, if I'll write a lot, I'll have no time to write here). So I start to check the material. Really, there is very few information regarding build VS addins, much less regarding creation of references, and nothing about creation web references. So let's deep into VS to understand what's happens there.

While adding web references (the references to Web Services), Visual Studio performs a couple of action. First it checks and retrieves the WS information, then it checks if the current project has local reference to System.Web.Services (the default WinForms project has not). The it discovered the service, by using discovery client protocol, collects the information regarding it's methods by using their public descriptors, then reveals XML schema and based on this schema generates web proxy by using WSDL. Wow, a lot of stuff. How to get rid on it?

Let's discover the world of DiscoveryClientProtocol from Discovery namespace of Web.Services assembly. It has a couple of really interesting methods such as Discover... or Resolve... By using those methods we can create the web service protocol

DiscoveryClientProtocol protocol = new DiscoveryClientProtocol();
            protocol.DiscoverAny(url);
            protocol.ResolveOneLevel();

The next step is to get descriptors. In order to do it, we'll iterate References property of the protocol and create ContractReference and DiscoveryReference for each of methods. We'll add them to our services collection



ServiceDescriptionCollection services = new ServiceDescriptionCollection();
            foreach (DictionaryEntry entry in protocol.References)
            {
                ContractReference contractRef = entry.Value as ContractReference;
                DiscoveryDocumentReference discoveryRef = entry.Value as DiscoveryDocumentReference;
                if (contractRef != null)
                {
                    services.Add(contractRef.Contract);
                }
            }


Let's get the schema of our service in order to generate proxy later it's pretty easy, especially when we have our protocol ready



XmlSchemas schemas = new XmlSchemas();
            foreach (DictionaryEntry entry in protocol.References)
            {
                SchemaReference schemaRef = entry.Value as SchemaReference;
                if (schemaRef != null)
                {
                    schemas.Add(schemaRef.Schema);
                }
            }

Next step is proxy (code) generation. In order to use it, we'll have to ask CodeDom and CodeCompile classes to help up. Actually, we can also call WSDL.exe to generate it, but if we already deep in code, let's create it



ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
 
            foreach (ServiceDescription description in serviceDescriptions)
            {
                importer.AddServiceDescription(description, null, null);
            }
 
            foreach (XmlSchema schema in schemas)
            {
                importer.Schemas.Add(schema);
            }
 
            System.CodeDom.CodeNamespace codeNamespace = new System.CodeDom.CodeNamespace(proxyNamespace);
            CodeCompileUnit codeUnit = new CodeCompileUnit();
            codeUnit.Namespaces.Add(codeNamespace);
            ServiceDescriptionImportWarnings warnings = importer.Import(codeNamespace, codeUnit);
 
            CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
            using (StreamWriter sw = new StreamWriter(fileName))
            {
                CodeGeneratorOptions options = new CodeGeneratorOptions();
                options.BracingStyle = "C";
                provider.GenerateCodeFromCompileUnit(codeUnit, sw, options);
            }

Now we have generated files (Reference.map with it's cs, Service.disco and Service.wsdl) all we have to do now is to put them together and reference somehow to our project.



string path = Path.GetDirectoryName(project.FullName)+@"\Web References";
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
 
            Directory.CreateDirectory(path + @"\" + name);
 
            path += @"\" + name;
 
 
            protocol.WriteAll(path, "Reference.map");
 
            ServiceDescriptionCollection services = GetServiceDescriptionCollection(protocol);
            XmlSchemas schemas = GetXmlSchemas(protocol);
            GenerateWebProxy(name, path + @"\Reference.cs", services, schemas);

So far so good. In order to do something with our project we'll build add-in for Visual Studio, that can pass us the current project and work with it. I'll not explain how to build addins here, ask Google Live to help :)


Just the small explanation for how to get current project, while in DTE _applicationObject.ActiveDocument we only have a document). DTE Document has something, called ProjectItem (we'll meet them closer later) inside this Item we have property named ContainingProject, that actually holds the parent project of this item.



internal static Project GetCurrentProject(Document currDoc)
        {
            if (currDoc == null)
                return null;
 
            ProjectItem prjItem = currDoc.ProjectItem;
            if (prjItem == null)
                return null;
 
            Project currPrj = prjItem.ContainingProject;
            if (currPrj == null)
                return null;
 
            return currPrj;
        }

Good, but in order to add reference to the project we need Visual Studio Project in other words VSProject. This special project holt by secret property of the Project named Object and their namespace is VSLangProj, that dug in VSLangProj assembly in Program Files\Microsoft Visual Studio 8\Common7\IDE\PublicAssemblies (are they really so "public" ? :)


Now let's take a look on this project. Oh my g-d! It has method named ADDWEBREFERENCE, that received url string as input. Just throw all you did off and use it in order to add web reference to your project. Smart VSTS will do all the rest for you!



 



internal static ProjectItem AddWebReference(Project project, string url, string name)
        {
            VSProject vsProj = project.Object as VSProject;
            if (vsProj == null)
                return null;
 
            ProjectItem item = vsProj.AddWebReference(url);
            try
            {
                item.Name = name;
            }
            catch (System.Runtime.InteropServices.COMException ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
 
                item.Remove();
            }
 
            return item;
 
        }

Have a nice day,  Visual Studio development and documentation team (this methods exists in MSDN, but researchable for some reason :))


Source code for this article

Monday, March 19, 2007

How to bind to Animation To and FROM properties

If you tried to bind to From and To values of Animation anything, you sure pay attention to the behavior, that the animation object just ignored you in the best case, in worth case, you couch very strange an annoying exception (if used from code-behind) "Cannot freeze the storyboard timeline tree for use across threads". What's the problem?

First of all, just note in your notepad: "When you start an animation, Timeline object copying itself frozen into internal boundary. There it creates Clock object and begin to run. So, how to change properties of the animation and make them occur immediately.

First of all, you can recreate whole storyboard. Rather costly and  very bad idea. You can make some woodoo on Completed event. For example, call invalidation of the animation to affect your changes. Good idea, but still a lot of mess. Another try is to work with FillBehavior. This one make you able to tell your animation to stop or to hold up for something else. Very good and very efficient method is to deal with internal clocks. For example, recreate or exchange them by your own. This is really hardcore. Maybe other day I'll explain this methods, but today, we'll just call new BeginAnimation to restart it. This method is not very efficient, but better then nothing. In order to do it, we'll deal today with EventTriggers.

So let's build the simple application, that continuously  changes the size of rectangle, based on values you provided with sliders. So, here we go.

First of all, let's create an animation

<Storyboard x:Key="myAnim">
      <ParallelTimeline>
        <DoubleAnimation
          Name="myAnimWidth"
          Storyboard.TargetName="myRect"
          Storyboard.TargetProperty="Width"
          From="100"
          To="500"
          Duration="0:0:5
          RepeatBehavior="Forever"
          AutoReverse="True"
                />
        <DoubleAnimation
          Name="myAnimHeight"
          Storyboard.TargetName="myRect"
          Storyboard.TargetProperty="Height"
          From="100"
          To="500"
          Duration="0:0:5"
          RepeatBehavior="Forever"
          AutoReverse="True"
                />
      </ParallelTimeline>
    </Storyboard>

We'll put in into resources in order us to be able to reuse and rerun it. The next step is to create two sliders, a couple of textblocks and the rectangle. I'll not rewrite a code here, 'cos it's really straight forward and we have nothing tricky with it. Now, I want to start the animation right after my page was loaded, so I'll add Page.Trigger to my Page.Loaded event. Pay attention, this method makes you able to almost any event from XAML. This is really cool feature. So, let's do it. 



<Page.Triggers>
    <EventTrigger RoutedEvent="Page.Loaded">
      <EventTrigger.Actions>
        <BeginStoryboard Storyboard="{StaticResource myAnim}"/>
      </EventTrigger.Actions>
    </EventTrigger>
  </Page.Triggers>

So far so good. The next step is to rerun the animation each time I'm changing the value of my sliders. As well as done with Page, Slider (as well as any other ContentPresenter has it's own event. So let's restart the animation on value change



<Slider Name="slFrom" Value="50" Width="150" Minimum="50" Maximum="600">
        <Slider.Triggers>
          <EventTrigger RoutedEvent="Slider.ValueChanged">
            <EventTrigger.Actions>
              <BeginStoryboard Storyboard="{StaticResource myAnim}"/>
            </EventTrigger.Actions>
          </EventTrigger>
        </Slider.Triggers>
      </Slider>

Rather simple, isn't it? We done, now we have our storyboard restarted and "invalidated" each time the bindings of From="{Binding ElementName=slFrom, Path=Value}" and To="{Binding ElementName=slTo, Path=Value}" where changed.


That's all, folks


Source code for this article

Sunday, March 18, 2007

Multithread logger in WPF

So, we already know to write very nice WPF application, but one of most common modules in every application is logger. How to use WPF capabilities to build smart logger. What to do with multithreading environment, when methods InvokeRequired is absent?

Today, we'll build logger, based on DataBinding of WPF, learn how to know what thread we are in and how to bind resources to ListView. Pretty enough? Let's start

First of all we have to build our XAML page. It sill consists of checkbox, binded to static variable that tells us if we have to log everything. So, we'll add simple boolean, named m_isDebug and bind it to the checkbox. But how? m_isDebug is simple variable, not dependency property, how to get it and bind to control. This part is really simple. We'll do it this way ("l" is our clr-namespace: xmlns:l="clr-namespace:WPFLogger")

<CheckBox IsChecked="{x:Static l:Window1.m_isDebug}">Debug view</CheckBox>

So far, so good. Now the main part of the application, our XmlDataSource - the log file. It's really simple to add the structure to our XAML

<Window.Resources>

  <XmlDataProvider x:Key="myDebugData" XPath="/myXmlData">
    <x:XData>
      <myXmlData xmlns="">
        <Item Date="00/00/00 00:00:00" Source="None" Message="Testing..."/>
      </myXmlData>
    </x:XData>
  </XmlDataProvider>
</Window.Resources>

Event simpler to get it from the code



protected void OnLoad(object sender, RoutedEventArgs e)


        {
            debug = this.Resources["myDebugData"] as XmlDataProvider;
            debug.IsAsynchronous = true;
            WriteLog(this, "Initialized...");            
        }


Let's build the static method, that knows to write to this Data Provider. This method should know where he called from in order us to be able to get debug information later. We do not have to leave our project in Debug mode to get StackTrace information. So, the method will looks like this:



if (debug != null && m_isDebug)
                {
                    System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(false);
 
 
                    XmlElement item = debug.Document.CreateElement("Item");
 
                    item.SetAttribute("Date", DateTime.Now.ToString("dd-MMM-yy HH:mm:ss.ffff"));
                    item.SetAttribute("Source", sender!=null?sender.ToString() + ".":Thread.CurrentThread.Name + stack.GetFrame(1).GetMethod().Name);
                    item.SetAttribute("Message", data);
 
                    debug.Document.SelectSingleNode(debug.XPath).AppendChild(item);
                }

StackTrace will tell us where we are together will caller attribute or thread information, if inaccessible. We are speaking about threads, is this method is thread safe? Not really. We are interacting with "debug" object, and updates ListView, so we have to be sure that we are in the right (STA) thread. There is no such thing InvokeRequired in WPF, due the new object, named Dispatcher tries to help us with thread synchronization. It knows what thread the caller exists and make us able to BeginInvoke and invoke our method within the right context. In order to get such information, we add a couple of things. First of all delegate to this method, next we'll add to the method check of context and then invoke or execute the rest



internal delegate void WriteLogDelegate(object sender, string data);
 
internal static void WriteLog(object sender, string data)
        {
            if (!Application.Current.Dispatcher.CheckAccess())
            {
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new WriteLogDelegate(WriteLog), sender, data);
            }
            else
            {
                if (debug != null && m_isDebug)
                {
                    System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(false);
 
 
                    XmlElement item = debug.Document.CreateElement("Item");
 
                    item.SetAttribute("Date", DateTime.Now.ToString("dd-MMM-yy HH:mm:ss.ffff"));
                    item.SetAttribute("Source", sender!=null?sender.ToString() + ".":Thread.CurrentThread.Name + stack.GetFrame(1).GetMethod().Name);
                    item.SetAttribute("Message", data);
 
                    debug.Document.SelectSingleNode(debug.XPath).AppendChild(item);
                }
            }
        }

The rest is really simple, we'll add listview, binded to our xml data provider, a couple of nice features, like saving, sorting etc within the data source. All the rest you'll see in the source code attached.


The usage of new WPF logger is really simple. Just call WriteLog(object, string) from any place in any thread you want and the result will appears in listbox immediately. The are a couple of FXes I'd add to the logger, such as priority, display or write flag and more, But all those are really application specific, so do it yourself :)


Source code for this article