Complicated bindings in WPF (Selectors and Converters)
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:
This converter was then used in the XAML as the Converter for the MultiBinding:
Now, that fixed one issue, but how can I now make the different MenuItems to
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:
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.
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
- have different looks depending on type/attributes
- behave differently depending on type/attributes
<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 theItemsControl 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