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
Cool. This is what I am looking for! 🙂
Hi Dmitry,
Thanks for the feedback (that demo files are wrong). It should be proper ones now.
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.
aaaaarggghhh, this demo is doomed. I swear I really uploaded the correct demo this time. Thanks for the feedback.
Yes, I tried to say you that, but there were something wrong with capcha
Truly appreciate this example.
This extension is now part of my tool bag.
Good work.
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.
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.