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.
0 comments:
Post a Comment