October 16, 2011

WPF ListView persistable layout - MVVM style

First of all, this article doesn't claim to show you 'the right way' to persist control's layout, it is just my effort to make my wpf UI a bit comfortable. This code works for me and has no warranties for you. Many data-centric applications use the ListView for table representation of data, allowing user to resize and move columns of the GridView. But when you open the application next time, all columns again have their default size and position and there is no way to save the layout of GridView and restore it on next load. Ok, let's try to add this feature to our control derived from Listiew. I think it is reasonable to use XamlWriter and XamlReader to serialize xaml content so all I need is to call XamlReader.Load method when our conrol initialize and XamlWriter.Save on column resize or move. As a MVVM adherent I would prefer to implement any persistence logic in the ViewModel class. Then our control should expose serialized layout state as a DependencyProperty and use binding to pass value to the correspondent ViewModel's property. Also I wanted a property IsLayoutPersistable to switch off the whole thing.
public static DependencyProperty IsLayoutPersistableProperty = 
   DependencyProperty.Register("IsLayoutPersistable", typeof(bool), typeof(PersistableLayoutListView), 
   new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits, 
   OnIsLayoutPersistableChanged));
public static DependencyProperty PersistableLayoutProperty = 
   DependencyProperty.Register("PersistableLayout", typeof(string), typeof(PersistableLayoutListView), 
   new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, 
   OnLayoutChanged));
There is no problem to get starting event just overriding OnApplyTemplate() method but how to get column resizing and moving event? The solution was to handle DragCompletedEvent that bubbling up from the Thumb element (as I found it's used as a columns separator in the header). We just need to set to true the argument handledEventsToo. This is for column resize. And GridView.Columns.CollectionChanged event could be handled to get column reordering.
((GridView)listView.View).Columns.CollectionChanged += _columnsCollectionChangedHandler;
listView.AddHandler(Thumb.DragCompletedEvent, _columnsResizeHandler, true);
Both the handlers would call column saving method that updates PersistableLayout bindable property:
var coll = ((GridView)listView.View).Columns;
string savedState = XamlWriter.Save(coll);
listView.PersistableLayout = savedState;
Further I need to handle changing of my IsLayoutPersistable dependency property to add/remove just desribed event handlers.
static void OnIsLayoutPersistableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var listView = (PersistableLayoutListView)d;
    var persistable = (bool)e.NewValue;
    AddRemoveHandlers(persistable, listView);
}
private static void AddRemoveHandlers(bool persistable, PersistableLayoutListView listView)
{
    if (persistable)
    {
        _columnsResizeHandler = new RoutedEventHandler((o, ea) =>
                                                           {
                                                               if (!(ea.OriginalSource is Thumb)
                                                                   || !ea.Handled)
                                                                   return;
                                                               SaveLayout(listView);
                                                           });
        _columnsCollectionChangedHandler = 
             new NotifyCollectionChangedEventHandler((o, ea) => SaveLayout(listView));
        ((GridView)listView.View).Columns.CollectionChanged += _columnsCollectionChangedHandler;
        listView.AddHandler(Thumb.DragCompletedEvent, _columnsResizeHandler, true);
    }
    else
    {
        if (_columnsCollectionChangedHandler != null) 
            ((GridView)listView.View).Columns.CollectionChanged -= _columnsCollectionChangedHandler;
        if (_columnsResizeHandler != null) 
            listView.RemoveHandler(Thumb.DragCompletedEvent, _columnsResizeHandler);
    }
}
Nothing special. Full source code for the control is here.

Usage.

You can use this control instead of regular ListView like this:
<WpfGui:PersistableLayoutListView x:Name="listView" 
      IsLayoutPersistable="True"
      PersistableLayout="{Binding Columns, Mode=TwoWay}"
      ...>
     <ListView.View>
       <GridView AllowsColumnReorder="True" >
          <GridViewColumn Width="50" Header="State" DisplayMemberBinding="{Binding State,Mode=OneWay}">
          ...
       </GridView>
    </ListView.View>
</WpfGui:PersistableLayoutListView>
And in the ViewModel:
public string Columns
{
    get { return columns ?? (columns = UserConfig.GetConfiguration("Columns")); }
    set
    {
        if (value == columns) return;
        columns = value;
        UserConfig.SetConfiguration(columns, "Columns");
    }
}
private string columns;
Here, in getter and setter you can use any preferrable read/write methods that persist xaml string to any storage. For example, a file in isolated storage or in the app directory. Full source code: PersistableLayoutListView.cs

October 11, 2011

One brick to the wall - common logging in .Net application


Being a .Net developer I often use an opensource log4net library in my projects. It needles to list all features  here, there is so many articles and posts about it, let me omit comparisons and move further.
That was my preferred logging library for many years though in last project I used NLog. That's my new favorite. Lightweight, easy to use, async- and bufferable on your wish (and the project is alive).
But what if I would want to change my preferences in logging and need to change the logging library afterwards? I need more flexible solution than just linking
NLog in my projects. And the solution is in Common.Logging namespace. Do you know? Right, the universal configurable adapter for logging frameworks. It let me use the logger-of-my-choice and not to be bound to specific library's classes.
Ok, then just add a reference on the Common.Logging.dll to the project either with a regular Add reference dialog or via NuGet (yes, it has a nu-package). My backend logger is NLog, so I need to add a NLog adapter as well, Common.Logging.NLog.
Then I need to add the following fragments to my app.config, according to this:
<sectionGroup name="common">
      <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
 </sectionGroup>
...and...
<common>
   <logging>
     <factoryAdapter type="Common.Logging.NLog.NLogLoggerFactoryAdapter, Common.Logging.NLog">
        <arg key="configType" value="FILE"/>
        <arg key="configFile" value="~/NLog.config"/>
     </factoryAdapter>
   </logging>
</common>
Now, I can configure Nlog through NLog.config as if I used it as standalone logger, with no adapters and excuses. Everything works like a charm.
But a trouble was on the way, my webapp has got a clone, and those two instances ought to log to one database table. What a mess, I need something to distinguish records of different apps. Something like ApplicationName or AppDomainName written in a standalone column. Reading NLog's documentation I found that best way to do it is using ${event-context} 'layout renderer'.
So if Nlog was a standalone logger all I need to do is to add a keyed value to the Properties dictionary in the LogEventInfo (the object that describes a log entry and declared in NLog.dll).
But unfortunately Common.Logging adapter does not provide any members to set the underlying logger's properties. Ok, let's implement custom NLog adapter, overloading the WriteInternal() method and adding 'Properties' property to the 'adapter' logger. Then we could use it the same way as in NLog, just by adding keyed values and referencing them in the config.
Full source you can find in the attachment.
Also, we need to overload one more thing from Commons library, the NLogLoggerFactoryAdapter. It's responsible for creation logger instances from the configuration. It is as simple as that:
public class NLogLoggerFactoryAdapterEx : NLogLoggerFactoryAdapter
{
    public NLogLoggerFactoryAdapterEx(NameValueCollection properties) 
       : base(properties)
    {
    }

    protected override ILog CreateLogger(string name)
    {
         return new My.Logging.NLogger(NLog.LogManager.GetLogger(name));
    }
}
And no line more. Now then we can use our extended logger slightly modifying both our configuration and code:
<common>
   <logging>
      <factoryAdapter type="My.Logging.NLogLoggerFactoryAdapterEx, My.Logging">
        <arg key="configType" value="FILE"/>
        <arg key="configFile" value="~/NLog.config"/>
      </factoryAdapter>
   </logging>
</common>
  ...and usage...
var logger = (My.Logging.NLogger)LogManager.GetCurrentClassLogger();
logger.Properties["appname"] = Properties.Settings.Default.ApplicationName; // any string 
  ... and in NLog.config...
<parameter name="@appName" layout="${event-context:item=appname}"/>
NB, if you use other Common.Logging adapter for your preferrable logging library you might want to implement Properties of type IDictionary<object,object> in your class.

Full source: NLogLoggerFactoryAdapterEx.cs