DevExpress’ FlowLayoutControl and MVVM

FlowLayoutControl unfortunately doesn’t support items binding. You can’t just provide a source and hope FlowLayoutControl will populate the content. But fear not, there is nothing attached properties can’t solve.

I’ve created an attached property ItemsSource that does all that for you. Here is its declaration:

public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.RegisterAttached("ItemsSource", typeof(IEnumerable), typeof(FlowLayoutExtensions), 
            new UIPropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));

It accepts an IEnumerable as an input.

And here is the relevant code when ItemsSource changes:

private static void OnItemsSourceChanged(DependencyObject o, IEnumerable oldValue, IEnumerable newValue)
{    
    FlowLayoutControl layout = o as FlowLayoutControl;
    if (layout != null)
    {

        NotifyCollectionChangedEventHandler collectionChanged = delegate(object s, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    AddItems(layout, e.NewItems);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    RemoveItems(layout, e.OldItems);
                    break;
            }
        };

        // remove event implementation
        if (oldValue != null)
        {
            INotifyCollectionChanged oldIncc = oldValue as INotifyCollectionChanged;
            if (oldIncc != null)
                oldIncc.CollectionChanged -= collectionChanged;
        }
        layout.Children.Clear();

        if (newValue != null)
        {
            AddItems(layout, newValue);
            INotifyCollectionChanged incc = newValue as INotifyCollectionChanged;
            if (incc != null)
            {
                incc.CollectionChanged += collectionChanged;
            }
        }
    }
}

First it defines a delegate that gets called upon collection changes (when source is INotifyCollectionChanged) then it unsubscribes from CollectionChanged if it has previously subscribed. And finally it populates FlowLayoutControl with items and optionally subscribes to CollectionChanged event (when source supports it). Note that ObservableCollection<T> implements INotifyCollectionChanged.

Here is the code that adds or removes items:

private static void AddItems(FlowLayoutControl layout, IEnumerable source)
{
    foreach (object item in source)
    {
        GroupBox box = new GroupBox { DataContext = item };
        layout.Children.Add(box);
    }
}

private static void RemoveItems(FlowLayoutControl layout, IEnumerable source)
{
    foreach (object item in source)
    {
        GroupBox match = (from gb in layout.Children.OfType<GroupBox>()
                          where gb.DataContext == item
                          select gb).FirstOrDefault();
        if (match != null)
            layout.Children.Remove(match);
    }
}

Add items adds an GroupBox instance for each new item and sets its DataContext to the item. While RemoveItems searches for a matching GroupBox (based on DataContext match) instance and removes it from the FlowLayoutControl's Children collection.

A bit of XAML is required as well. I control the GroupBox appearance through a Style, like this:

<Style TargetType="dxlc:GroupBox">
    <Setter Property="MaximizeElementVisibility" Value="Visible"/>
    <Setter Property="MinimizeElementVisibility" Value="Visible"/>
    <Setter Property="Width" Value="150"/>
    <Setter Property="Header" Value="{Binding Caption}" />
    <Setter Property="Content" Value="{Binding}" />
</Style>

Note the binding of the Content property (remember, I am assigning current item as DataContext). And here is the FlowLayoutControl instance declaration:

<dxlc:FlowLayoutControl loc:FlowLayoutExtensions.ItemsSource="{Binding}" />

There you go, a MVVM friendly approach.

Note that this is not a fully featured code but it is a good starting point.

31.8.2011 - correct demo files

20.9.2011 - ufff, again uploaded really proper demo

FlowLayoutExtensionsDemo.zip (9.99 kb)

Comments (8) -

  • dmitry

    31.8.2011 16:40:49 | Reply

    Cool. This is what I am looking for! Smile

    • Miha Markic

      31.8.2011 16:54:05 | Reply

      Hi Dmitry,

      Thanks for the feedback (that demo files are wrong). It should be proper ones now.

      • Marko

        20.9.2011 10:29:54 | Reply

        The demo file is still wrong.
        It contains no usefull code at all. There is just App and MainWindow (both of them empty) in the project. And there's no flow layout extensions present.

        • Miha Markic

          20.9.2011 10:50:16 | Reply

          aaaaarggghhh, this demo is doomed. I swear I really uploaded the correct demo this time. Thanks for the feedback.

  • dmitry

    31.8.2011 19:56:17 | Reply

    Yes, I tried to say you that, but there were something wrong with capcha

  • Pierre Lamontagne

    1.12.2011 17:17:36 | Reply

    Truly appreciate this example.
    This extension is now part of my tool bag.
    Good work.

  • Mark

    2.12.2011 15:54:45 | Reply

    There's a bug in your code - the un-subscription from the CollectionChanged event will never work, because a new delegate is created each time the static function is called.

    • Miha Markic

      3.12.2011 11:53:46 | Reply

      Hi Mark,

      That's a good point. However, the code should work nevertheless unless I am mistaken Smile.
      The compiler creates an anonymous method (delegate implementation) and each time a delegate is created it points to that method. Thus they are treated as equal and correctly removed when required. Perhaps the notation is misleading.

Loading