... a journey through WPF, MVVM and .NET4 RSS 2.0
# Tuesday, August 11, 2009

One of the new features added to WPF 3.5 SP1 was the ScrollViewer.IsDeferredScrollingEnabled property which allows you to scroll through large amounts of data without taking the actual scrolling hit until the user stops moving the scroll thumb.  This is essential when you have complex visualizations, or if the data itself is virtualized and the load time is expensive.  You can get information on this feature here:

http://msdn.microsoft.com/en-us/library/system.windows.controls.scrollviewer.isdeferredscrollingenabled.aspx

Recently, I was working on a project where I have a Slider that is controlling a grouping option.  When the user changes the slider value, the visuals are regrouped based on the position of the slider itself.  The problem I ran into was it's an expensive operation to do the grouping and if the user tries to quickly drag the slider I ended up doing a whole bunch of non-essential groupings of my data for no reason.  They are expensive enough that the actual drag of the slider was impacted by it - not to mention the CPU and rendering!  So, my initial response in these situations is to turn on asynchronous data binding - that's as simple as throwing the Binding.IsAsync flag.  This essentially causes the binding transfer to occur on a background thread and will often improve responsiveness in situations like this.

In this case, however, it didn't really help because I was still doing all the intermediate calculations.  What I really need is some way to defer the binding transfer until the slider stops. There's no option for that however so I whipped up a simple class which allows me to bind two properties together and place a timer between them to indicate how long it should wait before transferring the Source to the Target.  I could have done it in the code - it's just a timer solution after all, but I wanted something reuseable.

Here's an example usage:

<StackPanel>
   <StackPanel.Resources>
      <DeferredBinder:DeferredBinding x:Key="dbTest" Timeout="1" />
   </StackPanel.Resources>
   <TextBlock x:Name="tb" Text="{Binding Source={StaticResource dbTest}, Path=Target}" FontSize="48pt" HorizontalAlignment="Center" />
   <Slider x:Name="slider" Margin="10" HorizontalAlignment="Center" Width="200" Minimum="0" Maximum="100" Value="{Binding Source={StaticResource dbTest}, Path=Source, Mode=OneWayToSource}" />
</StackPanel>

In this case, the slider's value is transferred 1 second after the final update (i.e. it waits 1 second for the user to stop sliding).

Here's a sample project to try out:

DeferredBindingSample.zip

 

Tuesday, August 11, 2009 5:54:00 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF

I'm a big fan of themes in WPF -- and I think it's great that Microsoft has released a whole set of themes for Silverlight and WPF at www.codeplex.com/wpf

However, in using some of those themes, I've run into an annoying bug in the RadioButton template - specifically, the checked state doesn't always show up initially.  Looking at the template, it turns out to be an easy fix.  The "CheckIcon" is set to an opacity of zero initially (so it's not shown) and then a trigger is used to apply an animation to change the value.  Unfortunately, it looks like the animation is switched - it applies when the checkbox is UNCHECKED vs. CHECKED.  So, two ways to fix it -- either change the initial opacity to "1" for the "CheckIcon" element in the control template, or go to the triggers in the control template for the RadioButton and swap the states so it looks like this:

<Trigger Property="IsChecked" Value="false" />
<Trigger Property="IsChecked" Value="True">
   <Trigger.EnterActions>
      <BeginStoryboard x:Name="CheckedOn_BeginStoryboard" Storyboard="{StaticResource CheckedOn}"/>
   </Trigger.EnterActions>
   <Trigger.ExitActions>
      <BeginStoryboard x:Name="CheckedOff_BeginStoryboard" Storyboard="{StaticResource CheckedOff}"/>
   </Trigger.ExitActions>
</Trigger>
Tuesday, August 11, 2009 11:30:13 AM (Central Daylight Time, UTC-05:00)  #    Comments [3] -
.NET | WPF
# Tuesday, August 04, 2009

I just put the latest version of the MVVM helpers online - mvvmhelpers.zip

There's a bunch of new stuff in it - check the release notes for the highlights.  There's a set of breaking changes in it as well, specifically I've moved several of the attached behaviors into the new Blend model.  Originally in my local version I did it to the JulMar.Wpf.Helpers.dll and got a dependency against System.Windows.Interactivity.dll.

I decided that for this release I didn't want to force that dependency so I created a secondary assembly JulMar.Wpf.Behaviors.dll which has all those behaviors in it.  The breaking change is I removed the original attached behaviors from the library (DoubleClickBehavior, NumericTextBehavior, MouseDragBehavior) in favor of using these new versions.  I did update the sample to show how they get used.

In a recent Essential WPF class, one of the students wanted an ObservableDictionary which we whipped up there - I cleaned up that implementation somewhat and added it into this library along with some unit tests for it.

Again, please check the readme included in the .zip file -- as always you are free to do whatever you like with this code.  If you do anything interesting or fun, let me know!

 

Tuesday, August 04, 2009 1:14:38 PM (Central Daylight Time, UTC-05:00)  #    Comments [3] -
.NET | WPF | MVVM
# Monday, August 03, 2009

A recent series of blog entries at http://themechanicalbride.blogspot.com/ introduced the Rx framework (System.Reactive.dll) which is an assembly used in the Silverlight toolkit for UI testing purposes.  It essentially provides a mechanism to do event driven programming through LINQ.  I'll refer you to the blog referenced above for all the gory details - frankly I'm still trying to wrap my mind around it!

Silverlight isn't my favorite technology however, I much prefer working in WPF and so I spent some time rebuilding Rx for the desktop CLR!  My original approach was to take my favorite exploration tool, reflector (available for free from www.redgate.com), and disassemble all the classes into C#, placing them into a project file.  I found however that Reflector choked on some of the more complicated structures - requiring me to go and hand-edit a bunch of the code.  I realized while I was doing this that there was a much easier way to convert a Silverlight assembly to a WPF assembly.

As you probably already know, Silverlight shares the same assembly format as the desktop CLR - there is no difference in the IL or structure of the assembly itself.  The difference is in the dependency on mscorlib and other references.  Specifically, when you add an assembly via VS2008, it looks at the version of the referenced mscorlib to determine whether it's a desktop CLR assembly or Silverlight assembly. 

Here's the header of a Silverlight assembly examined through ILDASM:

// Metadata version: v2.0.50727
.assembly extern mscorlib
{
   .publickeytoken = (7C EC 85 D7 BE A7 79 8E ) // |.....y.
   .ver 2:0:5:0
}

Here is a desktop assembly:

// Metadata version: v2.0.50727
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 2:0:0:0
}

Notice the version difference .. Silverlight uses 2.0.5.0 of mscorlib and the desktop CLR uses 2.0.0.0.  The public key is also different, but this is really just used for loading purposes.

Assuming the assembly doesn't use something Silverlight specific, we can modify the assembly references and allow the asembly to be used in the desktop CLR fairly easily. 

Here's how I did it for System.Reactive.dll:

ILDASM System.Reactive.dll /out:SR.il

change the assembly references in the resulting IL text file (for mscorlib, system and system.core)

ILASM SR.il /resource=sr.res /output=WPF/System.Reactive.dll

That gave me a desktop version of the assembly without modifying anything in the code itself - much easier than my original reflector route!

Next, I took a couple of the samples from the blog and ported them to WPF - specifically, I took the events sample listed here http://themechanicalbride.blogspot.com/2009/07/developing-with-rx-part-1-extension.html and ported it to WPF to test it.  I made very few changes, primarily just switching Application.Current.MainWindow for Application.Current.RootVisual.  Here is the resulting project and switched assembly for anyone who is interested in it.

ExtensionEvents.zip (44.62 KB)

I think Rx is a fascinating piece of code, although it will take me a bit of time to realize just what I can do with it I suspect.  I'm looking forward to building more with this using WPF now that's for sure!

Monday, August 03, 2009 10:56:03 AM (Central Daylight Time, UTC-05:00)  #    Comments [1] -
.NET | Code | WPF
# Friday, July 24, 2009

One of the coolest new features of Blend 3 is the inclusion of behaviors. This new feature formalizes the "attached behavior" model that has become so prevelant in WPF (and Silverlight) development.  I won't go into details on the architecture - instead I'll refer you to a nice reference:

http://blogs.msdn.com/expression/archive/2009/05/19/link-round-up-behaviors-related-posts.aspx

To play with this new support, I built a WatermarkTextBehavior which places a watermark into a TextBox when it has no entered data.  I've included this new behavior into the current build of my MVVM toolkit which I'll release soon, but for now, let's look at the behavior:

First, we derive from System.Windows.Interactivity.Behavior<T> - the placeholder parameter is the Visual type you want the behavior to act on.  This can be UIElement for anything WPF, or more restrictive if necessary based on the events you intend to hook up.  For our purposes here, we will set the restricted type to TextBox.

public class WatermarkTextBehavior : Behavior<TextBox>

Next, you override the OnAttached() and OnDetaching() methods to hook up your event behaviors you desire.  Call the base implementation first, and then the AssociatedObject property will be the element you've been attached to (the TextBox in this case).  In our case we want to hook the GotFocus and LostFocus events - this is where we trigger our behavior.

protected override void OnAttached()
{
  
base.OnAttached();
  
AssociatedObject.GotFocus += OnGotFocus;
  
AssociatedObject.LostFocus += OnLostFocus;
  
...
}

protected override void OnDetaching()
{
   base.OnDetaching();
   AssociatedObject.GotFocus -= OnGotFocus;
   AssociatedObject.LostFocus -= OnLostFocus;
}

Finally, we can provide any properties necessary to drive our behavior.  These should be done in the form of Dependency Properties so they are bindable and interact nicely with WPF.  The base Behavior<T> derives from Freezable and inherits the DataContext automatically to enable this support.  In our implementation we will have a Text property to indicate the watermark, and an attached property which we will place onto the TextBox so it can be styled when the watermark is being used.

public static readonly DependencyProperty TextProperty =
  
DependencyProperty.Register("Text", typeof (string), typeof (WatermarkTextBehavior),
                     
new FrameworkPropertyMetadata(string.Empty));

Next we can hook it up in Blend through the Asset panel - all known assets are shown here (either registered, in the Blend directory, or project references).  Drag our WatermarkTextBehavior onto any TextBox and set the Text property and it will generate the following XAML:

<TextBox>
   <TextBox.Style>
      <Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
         <Style.Triggers>
            <Trigger Property="julmar:WatermarkTextBehavior.IsWatermarked" Value="True">
               <Setter Property="Foreground" Value="Gray" /> 
               <Setter Property="FontStyle" Value="Italic" />
            </Trigger>
         </Style.Triggers>
      </Style>
   </TextBox.Style>
   <i:Interaction.Behaviors>
      <julmar:WatermarkTextBehavior Text="Enter a name here" />
   </i:Interaction.Behaviors>
</TextBox>

Here is the complete source code to the behavior:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace JulMar.Windows.Interactivity
{
    /// <summary>
    /// This behavior associates a watermark onto a TextBox indicating what the user should
    /// provide as input.
    /// </summary>
    public class WatermarkTextBehavior : Behavior<TextBox>
    {
        /// <summary>
        /// The watermark text
        /// </summary>
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof (string), typeof (WatermarkTextBehavior),
                                        new FrameworkPropertyMetadata(string.Empty));

        static readonly DependencyPropertyKey IsWatermarkedPropertyKey =
            DependencyProperty.RegisterAttachedReadOnly("IsWatermarked", typeof(bool), typeof(WatermarkTextBehavior), 
                                        new FrameworkPropertyMetadata(false));

        /// <summary>
        /// This readonly property is applied to the TextBox and indicates whether the watermark
        /// is currently being displayed.  It allows a style to change the visual appearanve of the
        /// TextBox.
        /// </summary>
        public static readonly DependencyProperty IsWatermarkedProperty = IsWatermarkedPropertyKey.DependencyProperty;

        /// <summary>
        /// Retrieves the current watermarked state of the TextBox.
        /// </summary>
        /// <param name="tb"></param>
        /// <returns></returns>
        public static bool GetIsWatermarked(TextBox tb)
        {
            return (bool) tb.GetValue(IsWatermarkedProperty);
        }

        /// <summary>
        /// Retrieves the current watermarked state of the TextBox.
        /// </summary>
        public bool IsWatermarked
        {
            get { return GetIsWatermarked(AssociatedObject);  }    
            private set { AssociatedObject.SetValue(IsWatermarkedPropertyKey, value);}
        }

        /// <summary>
        /// The watermark text
        /// </summary>
        public string Text
        {
            get { return (string) base.GetValue(TextProperty); }
            set { base.SetValue(TextProperty, value); }
        }

        /// <summary>
        /// Called after the behavior is attached to an AssociatedObject.
        /// </summary>
        /// <remarks>
        /// Override this to hook up functionality to the AssociatedObject.
        /// </remarks>
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.GotFocus += OnGotFocus;
            AssociatedObject.LostFocus += OnLostFocus;

            OnLostFocus(null, null);
        }

        /// <summary>
        /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        /// <remarks>
        /// Override this to unhook functionality from the AssociatedObject.
        /// </remarks>
        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.GotFocus -= OnGotFocus;
            AssociatedObject.LostFocus -= OnLostFocus;
        }

        /// <summary>
        /// This method is called when the textbox gains focus.  It removes the watermark.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnGotFocus(object sender, RoutedEventArgs e)
        {
            if (string.Compare(AssociatedObject.Text, this.Text, StringComparison.OrdinalIgnoreCase) == 0)
            {
                AssociatedObject.Text = string.Empty;
                IsWatermarked = false;
            }
        }

        /// <summary>
        /// This method is called when focus is lost from the TextBox.  It puts the watermark
        /// into place if no text is in the textbox.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnLostFocus(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(AssociatedObject.Text))
            {
                AssociatedObject.Text = this.Text;
                IsWatermarked = true;
            }
        }
    }
}

 

Friday, July 24, 2009 2:14:52 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | Code | WPF
# Monday, July 20, 2009

Thanks to all of you who attended my class last week in Chicago - here are the demos:

files.me.com/mark.c.smith/ty3x8t

and here is that final presentation which was not in the books:

files.me.com/mark.c.smith/0wdurs

The password for both is your company name in lower case.

Monday, July 20, 2009 9:19:31 AM (Central Daylight Time, UTC-05:00)  #    Comments [0] -

# Monday, July 06, 2009

A project I've been working on for the last two months is finally online in beta form - check out

http://rcat.codeplex.com/

It's essentially a biological alignment viewer for RNA sequences.  Here's a couple of screen shots:

Alignment Viewer

2D Structure Viewer

2D Circle relationship Viewer

You can download the source code from the above link to play with it - it's a MVVM implementation and has quite a bit of interesting optimizations in it for performance purposes, including a read-only data virtualization for large sequence manipulation.  The next release will include a full read/write implementation so watch the project if you are interested!

 

Monday, July 06, 2009 1:24:44 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | Code | Real World | WPF
# Wednesday, April 29, 2009

In the previous post, I wrote about using the MVVM pattern with Context Menus, but you can use it with top-level menus too -- for example, perhaps I'd like to manage a set of options through a set of checkboxes.  First, let's start with my list of options - for simplicity, we'll just make it an enumeration:

public enum Test
{
   A,
   B,
   C,
   D,
   E,
   F
}

Next, let's define a ViewModel to wrap this - each entry should support a single value and a boolean binding to determine whether it is checked:

public class CheckValues
{
   public Test Value { get; set; }
   public string Text { get { return Value.ToString(); } }
   public bool IsChecked { get; set; }
}

Very likely this object would also implement INotifyPropertyChanged and fire change notifications, in this case it's not necessary since we aren't changing the values from code behind.  The next step is to expose this as a bindable collection - since it's not changing we'll just use a List<T>.

public List<CheckValues> EnumValues { get; set; }

...

EnumValues = new List<CheckValues>();
foreach (object t in Enum.GetValues(typeof (Test)))
{
   EnumValues.Add(new CheckValues {Value = (Test) t});
}

Finally, in the XAML itself we want to databind to the collection of CheckValues, supplying the appropriate support for each MenuItem to bind the menu checks to the collection:

<Menu DockPanel.Dock="Top">
   <MenuItem Header="Enum Values" ItemsSource="{Binding EnumValues}">
      <MenuItem.ItemContainerStyle>
         <Style TargetType="MenuItem">
            <Setter Property="Header" Value="{Binding Text}" />
            <Setter Property="IsCheckable" Value="True" />
            <Setter Property="IsChecked" Value="{Binding IsChecked}" />
         </Style>
      </MenuItem.ItemContainerStyle>
   </MenuItem>
</Menu>

That gives us our nice "checkable" menu - when the user clicks a checkmark, it sets the appropriate boolean in the code behind which could trigger logic, set values, etc.

But wait!  Often with checkboxes you want mutual exclusive choices - like the RadioButton groups.  This was a feature of Windows Forms which unfortunately did not make it into WPF, but we can make it work that way.  We could use the above logic and have the selection of a specific CheckValue unselect any others, or we could take advantage of RadioButtons to do this -- unfortunately I've not found a way that doesn't invoice a little code behind so it's not quite as clean as I'd like it to be, but it is possible.

First, let's define an exclusive ViewModel object, here we will track the "current" selection:

public class ExclusiveCheckValues
{
   public static Test SelectedValue { get; set; }
   public Test Value { get; set; }

   public string Text { get { return Value.ToString(); } }
   public bool IsChecked
   {
      get { return Value == SelectedValue; }
      set
      {
         if (value)
            SelectedValue = Value;
      }
   }
}

Next, we populate it into a bindable collection just as before and then we create some slightly more complex XAML for our menu items.  The trick here is to put a RadioButton into the menu item and associate it with a group.  I have to give a shout out to Dr. WPF from the wpf-disciples list some kudos here - he posted this trick on the MSDN forums (http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/76c14163-9462-44d6-b0eb-6f22a006b423).  It turns out the MenuItem.Icon is positioned just where the normal check mark would be so if we place a styled RadioButton as the MenuItem.Icon we can make it look just like the checkmark:

<Menu DockPanel.Dock="Top">

   <Menu.Resources>
      <RadioButton x:Key="rb" x:Shared="false" HorizontalAlignment="Left"
                   GroupName="MenuGroup" IsChecked="{Binding IsChecked}">
         <RadioButton.Template>
            <ControlTemplate TargetType="{x:Type RadioButton}">
               <Path x:Name="check" Margin="7,0,0,0" Visibility="Collapsed" VerticalAlignment="Center"
                      Fill="{TemplateBinding MenuItem.Foreground}" FlowDirection="LeftToRight"
                      Data="M 0,5.1 L 1.7,5.2 L 3.4,7.1 L 8,0.4 L 9.2,0 L 3.3,10.8 Z"/>
               <ControlTemplate.Triggers>
                   <Trigger Property="IsChecked" Value="True">
                      <Setter TargetName="check" Property="Visibility" Value="Visible" />
                   </Trigger>
                </ControlTemplate.Triggers>
             </ControlTemplate>
          </RadioButton.Template>
       </RadioButton>
    </Menu.Resources>

    <MenuItem Header="Single Select Enum Values" ItemsSource="{Binding ExclusiveEnumValues}">
       <MenuItem.ItemContainerStyle>
          <Style TargetType="MenuItem">
             <Setter Property="Header" Value="{Binding Text}" />
             <Setter Property="Icon" Value="{DynamicResource rb}" />
             <EventSetter Event="Click" Handler="OnMenuItemClick" />
          </Style>
       </MenuItem.ItemContainerStyle>
    </MenuItem>
</Menu>

Here, we've placed the RadioButton into our resources - but marked it as non-shareable.  The issue is that Styles always share property setters - and if you tried to do this:

<Style TargetType="MenuItem">
   <Setter Property="Icon"
>
     <Setter.Value>
       <RadioButton ... />
     </Setter.Value>
   </Setter>

You will get a very strange error message: "Cannot assigned type 'RadioButton' to type 'object'".  Which makes absolutely no sense until you realize the XAML parser is trying to assign the same RadioButton to every MenuItem.Icon.  The non-shareable resource fixes that issue.  The last step is to set the check -- this is done through the EventSetter in the above template.  It's wired to the little bit of code behind:

private void OnMenuItemClick(object sender, RoutedEventArgs e)
{
   ((RadioButton) ((MenuItem) sender).Icon).IsChecked = true;
}

This then sets the IsChecked property which will, in turn, select that item.  Now you can look at ExclusiveCheckValue.SelectedValue to get the current item.  This isn't the most elegant solution, nor is it the only solution but it's an interesting one to think on.  If I were truly building this into a production application, I'd probably look at controlling the selection in the ModelView (i.e. enumerate through my other choices and deliberately turn them off).  But this does illustrate how powerful WPF is!

Here's the example project with both styles.


 

Wednesday, April 29, 2009 8:30:51 AM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF
# Tuesday, April 21, 2009

One question I've fielded a couple of times is how to manage menus, primarily context menus, with the MVVM pattern.  It turns out to be pretty easy once you know the "trick".  The key thing to keep in mind is that menus are just ItemsControls - they support data templating and binding like any other ItemsControl.  However, the part where people get lost is in hooking up the commands.  Here's the trick:

Step 1: Define some code behind construct to manage each menu item in a hiearchial fashion.  Here's one I've used:

public class MenuItem
{
   public string Text { get; set; }
   public List<MenuItem> Children { get; private set; }
   public ICommand Command { get; set; }
  
   public MenuItem(string item)
   {
       Text = item;
       Children = new List<MenuItem>();
   }
}

Step 2: Create your menu structure using the above container.

This involves just creating a List<MenuItem> which holds the root nodes and using a DelegatingCommand to wire up some code behind method.  Here's an example:

public List<MenuItem> MenuOptions
{
    get  {

       var menu = new List<MenuItem>();
       if (SupportedFileFormats.Count > 0)
      {
          var mi = new MenuItem("O_pen");
          foreach(var fl in SupportedFileFormats)
          {
               var sff = fl;
               mi.Children.Add(new MenuItem(fl.Attributes.Description) 
                      { Command = new DelegatingCommand(() => { LoadFromFormat(sff); })});
          }
          menu.Add(mi);
       }

       menu.Add(new MenuItem("Close _All") { Command = new DelegatingCommand(OnCloseAll, () => FileList.Count > 0)});
       return menu;
    }

}

Note the use of an anonymous method to suck the correct file format into the command handler.  A nice trick so the command is executed with some contextual information of what you clicked on.

Finally, you need to setup the context menu style:

<Style x:Key="ContextMenuItemStyle">
  
<Setter Property="MenuItem.Header" Value="{Binding Text
}"/>
   
<Setter Property="MenuItem.ItemsSource" Value="{Binding Children
}"/>
   
<Setter Property="MenuItem.Command" Value="{Binding Command
}" />
</Style>

And then use the style where you want the menu to appear:

<StackPanel Orientation="Horizontal">
   
<Image Source="{Binding Image}" Width="16" Height="16" />
   
<TextBlock Margin="5" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding Header}" />
   
<StackPanel.ContextMenu>
      
<ContextMenu ItemContainerStyle="{StaticResource ContextMenuItemStyle}" ItemsSource="{Binding MenuOptions}" />
   
</StackPanel.ContextMenu>
</StackPanel>

</HierarchicalDataTemplate>

Now the context menu is populated and properly dispatches to your ViewModel commands!

Edit: added intermediate object in LINQ query to fix deferred query issue pointed out in comments.
 

Tuesday, April 21, 2009 5:35:59 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
.NET | WPF

I recently wrote about the mini-library I tend to use, but Bill Kempf (one of the WPF disciples) released a MVVM library for WPF called Onyx which looks very appealing. I especially like his use of static reflection to implement INotifyPropertyChanged. Sweet Bill! Thanks for sharing!


Check it out: WPF Onyx

Tuesday, April 21, 2009 2:29:47 PM (Central Daylight Time, UTC-05:00)  #    Comments [0] -
WPF
I'm a WPF Disciple
Search
Categories
Archive
<August 2009>
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
303112345
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Mark Smith
Sign In
All Content © 2010, Mark Smith
DasBlog theme 'Business' created by Christoph De Baene (delarou)