Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Wednesday, May 19, 2010

CAL and AutoMapper

I’m using Composite Application Library (CAL) in a project. I decided to use AutoMapper as well. I put AutoMapper’s Mapper.CreateMap<source, dest>() calls into one of the Bootstrapper.cs methods. I got an InvalidOperationException in the ModuleCatalog’s InnerLoad() function.

Reason

AutoMapper creates an in-memory assembly and CAL tries to read all the assemblies’ Location property. This property raises an InvalidOperationException in case of in-memory assemblies.

Solution

Insert AutoMapper mappings right after the Bootstrapper.Run() call. In my case I call this in App.xaml.cs, so you can write the following:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    // Run bootstrapper
    Bootstrapper bootstrapper = new Bootstrapper();
    bootstrapper.Run();

    // AutoMapper mappings
    Parallel.Invoke(CreateAutoMapperMappings);
}

/// <summary>
/// Creates the AutoMapper mappings.
/// </summary>
private static void CreateAutoMapperMappings()
{
    Mapper.CreateMap<Source, Dest>();
}

Sunday, May 2, 2010

Get WPF DataGrid row and cell

There are no simple built-in methods for accessing individual rows or columns in WPF DataGrid. The following code samples will provide simple methods for accessing these items.

First of all we need a helper function for selecting a visual child:

public static T GetVisualChild<T>(Visual parent) where T : Visual
{
    T child = default(T);
    int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < numVisuals; i++)
    {
        Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
        child = v as T;
        if (child == null)
        {
            child = GetVisualChild<T>(v);
        }
       if (child != null)
       {
           break;
       }
   }
       return child;
}

There is a simple method for getting the current (selected) row of the DataGrid:

public static DataGridRow GetSelectedRow(this DataGrid grid)
{
    return (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(grid.SelectedItem);
}

We can also get a row by its indices:

public static DataGridRow GetRow(this DataGrid grid, int index)
{
    DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
    if (row == null)
    {
        // May be virtualized, bring into view and try again.
        grid.UpdateLayout();
        grid.ScrollIntoView(grid.Items[index]);
        row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
    }
    return row;
}

Now we can get a cell of a DataGrid by an existing row:

public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
    if (row != null)
    {
        DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

        if (presenter == null)
        {
            grid.ScrollIntoView(row, grid.Columns[column]);
            presenter = GetVisualChild<DataGridCellsPresenter>(row);
        }

        DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
        return cell;
    }
    return null;
}

Or we can simply select a row by its indices:

public static DataGridCell GetCell(this DataGrid grid, int row, int column)
{
    DataGridRow rowContainer = grid.GetRow(row);
    return grid.GetCell(rowContainer, column);
}

The functions above are extension methods. Their use is simple:

var selectedRow = grid.GetSelectedRow();
var columnCell = grid.GetCell(selectedRow, 0);

View source on Google Code (unformatted selection possible)

Saturday, April 17, 2010

Mixed mode assemblies on .NET 4.0

Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information.

I got this message when I tried to start a project migrated from .NET 3.5 to .NET 4.0. I case of migration, migration wizard inserts a <startup></startup> tag into the app.config file.

Solution: insert the

useLegacyV2RuntimeActivationPolicy="true"

attribute into the startup tag. Application should work now.

Reason: .NET 4.0 changed mixed mode assembly loading mechanisms.

NHibernate session management

When you start working with NHibernate, you will see code examples like this one:

int userID = 1234;
using( ISession session = sessionFactory.OpenSession() )
using( session.BeginTransaction() ) {
    User user = session.Get<User>(userID); 
    session.Transaction.Commit();
}

You get a session object, start a transaction and finally commit that. This is fine when you work in a simple one-layer application or you are OK with tight coupling. When you have multiple layers, or working with binding like in WPF, things get complicated.

Session management strategies

  1. Session per application
    Simplest approach. Just one session for the whole application, typically returned by a singleton.
    Problem: Async calls can interfere with each other, cache size grows, unrecoverable exceptions, stale data
  2. Session per screen
    New session for every form/view/… There are multiple concurrent sessions.
    Problem: You can keep a screen opened for a long time, so stale data is expectable.
  3. Session per data access
    Every single data access uses a new session object.
    Problem: Lazy loading won’t work, problem with 1st level cache…
  4. Session per use-case
    You create a new session for every use-case. There will be concurrent sessions.

Firs try: Following examples – Session per data access

I’ve created an application with the simplest solution possible. I followed the examples and whenever I tried to access data, I created a session and a transaction. Everything was fine, until I tried to employ lazy loading. It simply did not work. There was no open sessions the lazy loader proxy could have used.

Second try: Session per application

I modified the application. I created a singleton that returned a session object. Every single data access used this session. The application worked fine – for a while. I used SQLite as DBMS, and SQLite has not the best threading support. So I got exceptions randomly. Sometimes a control was empty, sometimes it was filled with data. Exceptions happened about 1 out of 15 tries. I suspected a threading issue and it seems it was.

Third try: Session per use-case

I had to implement a session per use-case (business transaction) solution to solve the problems above. A business transaction is a user-initiated action involving multiple complicated operations (steps). All operations (and the transaction itself) are either successful or unsuccessful. The state of the involved members are well-defined and consistent at the end of the transaction.

We have to be able to keep alive the NHiberanate ISession object over multiple operations. The solution is the unit of work pattern. It encapsulates the ISession object, so we can pass it around and implement the IDisposable pattern. The class implementing the unit of work pattern can expose ISession functionality. See the example UML class diagram below.

Illustration: Unit of Work 

This is an example of calling the UnitOfWork object instance:

using (UnitOfWork unitOfWork = database.CreateUnitOfWork())
{
    PropertiesRepository propertiesRepository = new PropertiesRepository(unitOfWork);
    properties = propertiesRepository.Current;
}

You can use a simple factory (it is the database object here) to create a new UnitOfWork instance. This instance should be passed to all the classes accessing NHibernate functionality. This is important, so design your data access and manipulating classes according to this constraint!

Link: Rich-client NHibernate session management
Link: Unit of Work

Sunday, January 24, 2010

Refresh WPF DataGrid

I am using WPF DataGrid for displaying a list of items. I had to modify an item, but the item had no INotifyPropertyChanged event on that property (it was a collection).

So my first idea was:

  1. Update the list behind ItemsSource
  2. Set ItemsSource property to null to force unbinding
  3. Set ItemsSource property to the new collection
  4. Set the SelectedItem to the new item
  5. Call ScrollIntoView() on the new item

Well, I got a huge exception at step 5. There must be a simpler solution. And there is.

The DataGrid generates a view of the ItemsSource. All I have to do is call the Items.Refresh() method, and it will rebind to the ItemsSource. Everything will be rebound, all updates will be visible. Selected item kept, visibility kept.

Monday, January 18, 2010

DispatcherPriority

I had a very tough problem today. The following code did not work correctly:

var books = _bookRepository.All;
 
Dispatcher.Invoke(
    new ThreadStart(delegate
                        {
                            foreach (Book book in books)
                            {
                                CatalogItems.Add(new BookViewModel(book));
                            }
 
                            OnLoaded(this, EventArgs.Empty);
                            OnPropertyChanged("CatalogItems");
                        }),
    DispatcherPriority.ApplicationIdle);

When I run the code above the first time, the debugger didn’t even break into the dispatched code. The subsequent calls were fine.

Finally I realized that DispatcherPriority.ApplicationIdle should be changed to DispatcherPriority.Normal – and everything was fine. Don’t forget to check the DispatcherPriority value of the dispatcher! It’s hard to debug and hard to recognize.

These are the possible dispatcher priority levels:

Priority

Description

Inactive

Work items are queued but not processed.

SystemIdle

Work items are only dispatched to the UI thread when the system is idle. This is the lowest priority of items that are actually processed.

ApplicationIdle

Work items are only dispatched to the UI thread when the application itself is idle.

ContextIdle

Work items are only dispatched to the UI thread after higher-priority work items are processed.

Background

Work items are dispatched after all layout, rendering, and input items are processed.

Input

Work items are dispatched to the UI thread at the same priority as user input.

Loaded

Work items are dispatched to the UI thread after all layout and rendering are complete.

Render

Work items are dispatched to the UI thread at the same priority as the rendering engine.

DataBind

Work items are dispatched to the UI thread at the same priority as data binding.

Normal

Work items are dispatched to the UI thread with normal priority. This is the priority at which most application work items should be dispatched.

Send

Work items are dispatched to the UI thread with the highest priority.

 

Link: Build More Responsive Apps With The Dispatcher (MSDN)

Monday, January 11, 2010

Events with anonymous delegate

I subscribed to an event with an anonymous delegate. Later I was in need for a solution to be able to unsubscribe this delegate. This is a simple solution for the problem:

public class ClassName
{
    MouseButtonEventHandler eventHandler;
 
    public ClassName()
    {
        eventHandler = delegate
                                        {
                                            // operations
                                        };
    }
 
    void Functionality()
    {
        if (_subscribe)
        {
            _object.MouseLeftButtonDown += eventHandler;
        }
        else
        {
            _object.MouseLeftButtonDown -= eventHandler;
        }
    }
}

Wednesday, December 16, 2009

Web-style WPF popup

I like modal web dialogs, especially when they dim the background. Here is an example:

WPFWebPopup WebExample

The background is dimmed, not clickable, and the dialog box is in the foreground. The work area is visible at first site, you must focus on the current content.

I wanted something similar in WPF. Here is my example application:

WPFWebPopup Window WPFWebPopup PoppedUp

How it works?

There are two parts of the window. There is a DataGrid in the left pane and a ListBox in the right pane. The same data is loaded into both controls. When you select an item in the grid, the popup will appear over the grid, displaying the selected name. Clicking on the popup will close that. You can’t click through the popup. The right pane demonstrates that you can do anything with other controls. The popup resizes itself (drag the grid splitter to test it).

About adorners

The key component to the popup control is the adorner class of WPF. The definition of adorners from MSDN:

An Adorner is a custom FrameworkElement that is bound to a UIElement. Adorners are rendered in an AdornerLayer, which is a rendering surface that is always on top of the adorned element or a collection of adorned elements. Rendering of an adorner is independent from rendering of the UIElement that the adorner is bound to. An adorner is typically positioned relative to the element to which it is bound, using the standard 2-D coordinate origin located at the upper-left of the adorned element.

There are three types of adorners:

Adorner: An abstract base class from which all concrete adorner implementations inherit.

AdornerLayer: A class representing a rendering layer for the adorner(s) of one or more adorned elements.

AdornerDecorator: A class that enables an adorner layer to be associated with a collection of elements.

The code

The code of the popup adorner is the following:

   1: using System.Windows;
   2: using System.Windows.Documents;
   3: using System.Windows.Media;
   4: using System.Windows.Controls;
   5:  
   6: namespace AdornerTest1
   7: {
   8:     public class PopUp : Adorner
   9:     {
  10:         private VisualCollection _Visuals;
  11:         private ContentPresenter _ContentPresenter;
  12:  
  13:         /// <summary>
  14:         /// Initializes a new instance of the <see cref="PopUp"/> class.
  15:         /// </summary>
  16:         /// <param name="adornedElement">The element to bind the adorner to.</param>
  17:         /// <exception cref="T:System.ArgumentNullException">Raised when adornedElement is null.</exception>
  18:         public PopUp(UIElement adornedElement)
  19:             : base(adornedElement)
  20:         {
  21:             _Visuals = new VisualCollection(this);
  22:             _ContentPresenter = new ContentPresenter();
  23:             _Visuals.Add(_ContentPresenter);
  24:         }
  25:  
  26:         /// <summary>
  27:         /// Initializes a new instance of the <see cref="PopUp"/> class.
  28:         /// </summary>
  29:         /// <param name="adornedElement">The adorned element.</param>
  30:         /// <param name="content">The content.</param>
  31:         public PopUp(UIElement adornedElement, Visual content)
  32:             : this(adornedElement)
  33:         { Content = content; }
  34:  
  35:         /// <summary>
  36:         /// Implements any custom measuring behavior for the adorner.
  37:         /// </summary>
  38:         /// <param name="constraint">A size to constrain the adorner to.</param>
  39:         /// <returns>
  40:         /// A <see cref="T:System.Windows.Size"/> object representing the amount of layout space needed by the adorner.
  41:         /// </returns>
  42:         protected override Size MeasureOverride(Size constraint)
  43:         {
  44:             _ContentPresenter.Measure(constraint);
  45:             return _ContentPresenter.DesiredSize;
  46:         }
  47:  
  48:         /// <summary>
  49:         /// When overridden in a derived class, positions child elements and determines a size for a <see cref="T:System.Windows.FrameworkElement"/> derived class.
  50:         /// </summary>
  51:         /// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
  52:         /// <returns>The actual size used.</returns>
  53:         protected override Size ArrangeOverride(Size finalSize)
  54:         {
  55:             _ContentPresenter.Arrange(new Rect(0, 0,
  56:                  finalSize.Width, finalSize.Height));
  57:             return _ContentPresenter.RenderSize;
  58:         }
  59:  
  60:         /// <summary>
  61:         /// Overrides <see cref="M:System.Windows.Media.Visual.GetVisualChild(System.Int32)"/>, and returns a child at the specified index from a collection of child elements.
  62:         /// </summary>
  63:         /// <param name="index">The zero-based index of the requested child element in the collection.</param>
  64:         /// <returns>
  65:         /// The requested child element. This should not return null; if the provided index is out of range, an exception is thrown.
  66:         /// </returns>
  67:         protected override Visual GetVisualChild(int index)
  68:         { return _Visuals[index]; }
  69:  
  70:         protected override int VisualChildrenCount
  71:         { get { return _Visuals.Count; } }
  72:  
  73:         /// <summary>
  74:         /// Gets or sets the content.
  75:         /// </summary>
  76:         /// <value>The content.</value>
  77:         public object Content
  78:         {
  79:             get { return _ContentPresenter.Content; }
  80:             set { _ContentPresenter.Content = value; }
  81:         }
  82:     }
  83: }

You can set a content for the popup adorner, and that is all what I want. The VisualCollection is responsible for rendering the content on screen, and the ContentPresenter is responsible for storing the popup’s content.

There is a BackgroundShade user control to represent the dimmed background. The code is very short and simple:

   1: <UserControl
   2:     [...]
   3:     Background="Black" 
   4:     Opacity="0.705">
   5:  
   6:     <Grid x:Name="LayoutRoot"/>
   7: </UserControl>

I set the background to black at line 3 and I set the opacity of the control to 70% at line 4.

The Main window layout:

   1: <Window
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:Custom="http://schemas.microsoft.com/wpf/2008/toolkit" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:AdornerTest1="clr-namespace:AdornerTest1"
   5:     x:Class="AdornerTest1.MainWindow"
   6:     x:Name="Window"
   7:     Title="Popup with shaded background sample"
   8:     Width="640" Height="480" mc:Ignorable="d">
   9:  
  10:     <Window.Resources>
  11:         <DataTemplate x:Key="ItemTemplate">
  12:             <StackPanel>
  13:                 <TextBlock Text="{Binding Name}" Background="Black" Foreground="White"/>
  14:                 <TextBlock Text="{Binding Phone}"/>
  15:                 <TextBlock Text="{Binding Note}" Margin="0 0 0 8"/>
  16:             </StackPanel>
  17:         </DataTemplate>
  18:     </Window.Resources>
  19:  
  20:     <Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource SampleDataSource}}">
  21:         <Grid.ColumnDefinitions>
  22:             <ColumnDefinition/>
  23:             <ColumnDefinition MinWidth="10" Width="Auto"/>
  24:             <ColumnDefinition/>
  25:         </Grid.ColumnDefinitions>
  26:         <Custom:DataGrid x:Name="Grid" ItemsSource="{Binding Collection}"  AutoGenerateColumns="True" SelectionChanged="Grid_SelectionChanged" />
  27:         <GridSplitter HorizontalAlignment="Stretch" Grid.Column="1"/>
  28:         <ListBox x:Name="List" Grid.Column="2" ItemTemplate="{DynamicResource ItemTemplate}" ItemsSource="{Binding Collection}" d:LayoutOverrides="GridBox"/>
  29:     </Grid>
  30: </Window>
There is a Grid as layout root with 3 columns. There is a DataGrid in the left pane, a GridSplitter in the middle pane, and a ListBox in the right pane.

The Main window code:

   1: using [...]
   2:  
   3: namespace AdornerTest1
   4: {
   5:     public partial class MainWindow : Window
   6:     {
   7:         public MainWindow()
   8:         { [...] }
   9:  
  10:         BackgroundShade shade;
  11:         PopUp adorner;
  12:  
  13:         private void Grid_SelectionChanged(
  14:             object sender, SelectionChangedEventArgs e)
  15:         {
  16:             // Create adorner
  17:             AdornerLayer layer = AdornerLayer.GetAdornerLayer(Grid);
  18:             adorner = new PopUp(Grid);
  19:             
  20:             // Close adorner
  21:             adorner.MouseLeftButtonDown += delegate
  22:                                        {
  23:                                            layer.Remove(adorner);
  24:                                        };
  25:             ((FrameworkElement) adorner.AdornedElement).
  26:                 SizeChanged += AdornedElement_SizeChanged;
  27:  
  28:             // Create shaded background
  29:             shade = new BackgroundShade();
  30:             AdornedElement_SizeChanged(this, null);
  31:             
  32:             // Create text
  33:             TextBlock text = new TextBlock();
  34:             text.Foreground = Brushes.White;
  35:             // Bind text to selected item
  36:             Binding binding = new Binding("Name");
  37:             binding.Source = Grid.SelectedItem;
  38:             text.SetBinding(TextBlock.TextProperty, binding);
  39:             // Register text
  40:             shade.LayoutRoot.Children.Add(text);
  41:  
  42:             // Display adorner
  43:             adorner.Content = shade;
  44:             layer.Add(adorner);
  45:         }
  46:  
  47:         /// <summary>
  48:         /// Adorned element's size changed
  49:         /// </summary>
  50:         void AdornedElement_SizeChanged(object sender, SizeChangedEventArgs e)
  51:         {
  52:             shade.Width = adorner.AdornedElement.RenderSize.Width;
  53:             shade.Height = adorner.AdornedElement.RenderSize.Height;
  54:         }
  55:     }
  56: }

The implementation details are in the comments. One interesting thing: I couldn’t simply bind the AdornedElement’s RenderSize to the shade’s width property, so it seems simpler to convert the AdornedElement to FrameworkElement and attach an event handler to the SizeChanged event.

You can download the source and the binaries from Google code, or you can simply browse source at the same place. Links are below.

Link: Demo source [Google Code]
Link: Demo executable [Google Code]
Link: Browse source [Google Code]