Update: When I had more time to thing I realised that a MultiBinding needs a Converter, but a simple Binding also has a Converter and I guess that is what I should have used. I will update the post when I get to work on Monday.

I started to build the prototype of a control and I hit a wall where WPF binding was concerned.

For example: I wanted to bind my control to a collection of Items which could be Groups, Sections or Items. Groups have more Sections and Items. Sections have more Items, but they should be displayed as a non clickable section with its Item children under it. Everything in a menu.

At first I used an XML data source, which allowed me to use XPath to select all the Items in a Group, all the Sections and all the Items in the Section like this: XPath="Item|Section|Section/Item". However, when switching to an object schema, the normal Path syntax was too restrictive to allow for this. Even with a MultiBinding, I could find no Path syntax in which to select children of a certain type and their children. However! MultiBinding uses a Converter to usually merge two or more collections together. I used it to get the children and their own children! So forget XAML in this case, just use code:

public class SectionHeaderConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
List<ToolBarItem> list = new List<ToolBarItem>();
foreach (IEnumerable<ToolBarItem> collection in values)
{
foreach (ToolBarItem element in collection)
{
list.Add(element);
var toolBarSectionHeader = element as ToolBarSectionHeader;
if (toolBarSectionHeader != null)
{
foreach (var item in toolBarSectionHeader.Items)
{
list.Add(item);
}
}
}
}
return list;
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}


This converter was then used in the XAML as the Converter for the MultiBinding:

<MultiBinding Converter="{StaticResource SectionHeaderConverter}">
<Binding Path="Items"/>
</MultiBinding>


Now, that fixed one issue, but how can I now make the different MenuItems to
  1. have different looks depending on type/attributes
  2. behave differently depending on type/attributes
but selectively, keeping the default style and datatemplate in other cases? At first, when I used the XML datasource, having an XPath to select the type of the node based on its name was not an option because the XAML XPath implementation does not allow for XPath functions! so I used a StyleSelector for the difference between Sections (unclickable, spanning the whole width of the menu strip) and normal menu items, then a DataTemplateSelector for the difference between Groups and Items. It is also here where I bound the MenuItem Click RoutedEventHandler to an EventHandler in the datasource item class.


<Menu
ItemsSource="{Binding ElementName=ToolBarCurrent,Path=Items}"
ItemContainerStyleSelector="{StaticResource MenuItemContainerStyleSelector}"
ItemTemplateSelector="{StaticResource MenuItemTemplateStyleSelector}"
></Menu>


I don't know if that is the best way to do it, but suddenly it was like a fog lifted from my eyes and instead of that dreaded XAML I had something I know and understand used in a simple fashion. It is beyond the scope of this post to show how specific implementations of the selectors above work, but there are two issues that I had to fix:
  • Since the styles were resources defined in the resources of the containing Grid, I had to find a way to reference the Grid object from the StyleSelector:
    This is done using the
    ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
    var style = (Style) ic.FindResource("SectionStyle");
  • Since the data templates were resources defined in the resources of the containing Grid, I had to find a way to reference the Grid object from the DataTemplateSelector:
    this seems to be the same problem, but actually the solution was quite different, I used the FindVisualParent helper method like this:
    var ic = Helper.FindVisualParent<Grid>(container);
    The source for it is underneath.


public static TParentItem FindVisualParent<TParentItem>(DependencyObject obj)
where TParentItem : DependencyObject
{
DependencyObject current = obj;
while (current != null)
{
var tCurrent = current as TParentItem;
if (tCurrent != null)
return tCurrent;
current = VisualTreeHelper.GetParent(current);
}
return null;
}
.

Also, regarding this particular task, I found a way to display MenuItems grouped by a property in this blog post and at first I used this solution only to find out that this type of grouping only applies to flat DataTemplates. And while it seemed to be working in my first attempts with a HierarchicalDataTemplate, the grouping was only applied to the first level and not to the next.

My conclusion: as in many "magical" technologies, the complexity of a lot of problems is beyond the ability of the technology designers to predict. Therefore the only (and often enough, the best) option is to use your own code.

Comments

Be the first to post a comment

Post a comment