Run-time error: InverseBooleanConverter not found - wpf

I have a problem attempting to follow the advice in:
How to bind inverse boolean properties in WPF?
When I use with ResourceDictionary, it give run-time error. InverseBooleanConverter not found.
XMAL as follows:
<UserControl x:Class="SMTF.MasterDataView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SMTF" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="466" d:DesignWidth="483">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../AppResource.xaml" />
<ResourceDictionary Source="../DefaultStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="200,12,0,0" Name="stkMain" VerticalAlignment="Top" >
<Grid Margin="4">
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource InverseBooleanConverter}}" >
<Border Grid.Column="2" Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl Content="{Binding Path=WorkspaceView}" ContentTemplate="{StaticResource WorkspacesTemplate}" Header="View" Style="{StaticResource MainHCCStyle}"/>
</Border>
</ContentControl>
</Grid>
<Grid DockPanel.Dock="Bottom" Margin="0,2,4,2">
<TextBlock HorizontalAlignment="Right">
<ToggleButton x:Name="VisibilityToggle" Focusable="False" Style="{StaticResource SMToggle}" Command ="{Binding ShowNew}" >
</ToggleButton>
<!--<ToggleButton x:Name="VisibilityToggle" Background="Transparent" Command ="{Binding ShowNew}" >
<Image Source="/Image/Add.png" Width="24" />
</ToggleButton>-->
</TextBlock>
</Grid>
<Grid Margin="4">
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource BoolToVisibility}}" >
<Border Grid.Column="2" Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl Content="{Binding Path=WorkspaceEdit}" ContentTemplate="{StaticResource WorkspacesTemplate}" Header="Configure" Style="{StaticResource MainHCCStyle}"/>
</Border>
</ContentControl>
</Grid>
</StackPanel>
</Grid>
</UserControl>
I'm using the same code provided in the link.
ie:
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBooleanConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
in the AppResource XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SMTF">
<vm:InverseBooleanConverter x:Key="InverseBoolToVisibility" />
.....
.....
</ResourceDictionary>
Thanks in advance
NS

An alternative to converters for style related binding is to use Style.Triggers, the following shows a canvas when checkbox IsChecked = false, which would otherwise require an InverseBooleanConverter.
<Canvas x:Name="Overlay">
<Canvas.Style>
<Style TargetType="Canvas">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=MyCheckbox}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked, ElementName=MyCheckbox}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
<Rectangle Canvas.ZIndex="3" Fill="#99333333" Height="25" Stroke="Transparent" Width="293" Canvas.Left="10" Canvas.Top="-25"/>
</Canvas>

The key you are using is not correct. You resource key is InverseBoolToVisibility, while you have used InverseBooleanConverter as key.
Change the resource key to refer to correct resource as
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource InverseBoolToVisibility}}" >
Also your implementation for convereter is wrong. If you want to change the Visibility based on Boolean inverse value update your converter code as:
public class InverseBooleanConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(Visibility))
throw new InvalidOperationException("The target must be a boolean");
if(!(bool)value)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}

Related

How to check the property value of the singleton(or static) class in the XAML?

There may be expressions that sound rude because I'm not a native English speaker.
I hope you to understand.
I am creating an application supporting the theme.
I would like to change the image in the XAML whenever the theme changes.
Currently, my requirements were implemented by using both cs code and XAML code as below.
<HierarchicalDataTemplate DataType="{x:Type models:SolutionStruct}" ItemsSource="{Binding Projects}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Converter={converters:ToImageByThemeConverter}, ConverterParameter='Solution'}" Width="16" Height="16" Margin="0 0 5 0">
</Image>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
The below code is the cs code to convert.
public class ToImageByThemeConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string result = string.Empty;
if (Theme.ThemeKind == ThemeKind.Dark)
{
if (parameter.ToString() == "Solution") result = "/Resources/solution.png";
else if (parameter.ToString() == "Project") result = "/Resources/project.png";
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider) => this;
}
The above code works well but I would like to implement the equal functionality by using only XAML code.
I think If I can check the property value of the Theme class then I can solve it.
Here are some of the vague codes that I hit upon to solve this problem. (not operate)
<HierarchicalDataTemplate DataType="{x:Type models:SolutionStruct}" ItemsSource="{Binding Projects}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="0 0 5 0">
<Image.Triggers>
<Trigger Binding Source="{Binding {x:Static Theme}, Path="{ThemeKind}"} Value="Dark">
<Setter Source="/Resources/solution_dark.png"/>
</Trigger>
</Triggers>
</Image>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
Of course, the above code does not operate because I design vaguely.
Is it possible to design a code that operates with a similar feel to the above code?
or if you have another way to solve this problem please let me know, I don't obsess my way.
Thank you for reading.
You can bind to static properties by enclosing the property path in parentheses.
Adding the corresponding DataTrigger to the DataTemplate the HierarchicalDataTemplate would become:
<HierarchicalDataTemplate DataType="{x:Type models:SolutionStruct}"
ItemsSource="{Binding Projects}">
<StackPanel Orientation="Horizontal">
<Image x:Name="Image"
Source="/Resources/solution.png" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=(Theme.ThemeKind)}" Value="{x:Static ThemeKind.Dark}">
<Setter TargetName="Image" Property="Source" Value="/Resources/solution_dark.png" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>

Drawing Ellipse Line/PolyLine with a double

I have been working with this example to draw bar graph. The bar part is done and it represents the volume/quantity in a transaction. It's an associated price in range 20 - 30. What I want now is to draw points to represent price associated with volumes and connect those points. Two changes I've made in EDIT part of the linked example (1) removed the TextBlock from the DataTemplate of ItemsControl and added an Ellipse instead and (2) edited the canvas to add price/volume axis label. Here's how it looks like now:
How to add those Ellipse in right position and connect those with Line/PolyLine?
EDIT
Here's what I've now in ItemsControl:
<ItemsControl ScrollViewer.CanContentScroll="True"
Height="135"
ItemsSource="{Binding RectCollection}"
Margin="50 0 50 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Width="20">
<Canvas.LayoutTransform>
<ScaleTransform ScaleY="-1"/>
</Canvas.LayoutTransform>
<Rectangle Width="18"
Margin="0 0 2 0"
VerticalAlignment="Bottom"
Opacity=".5" Fill="LightGray">
<Rectangle.Height>
<MultiBinding Converter="{StaticResource VConverter}">
<Binding Path="ActualHeight"
RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding Path="DataContext.HighestPoint"
RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding Path="Volume"/>
</MultiBinding>
</Rectangle.Height>
</Rectangle>
<Line Stroke="DarkGreen" StrokeThickness="1"
X1="10" X2="30"
Y2="{Binding PreviousPrice, Converter={StaticResource PConverter}}"
Y1="{Binding CurrentPrice, Converter={StaticResource PConverter}}">
<Line.Style>
<Style TargetType="Line">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=PreviousPrice}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Line.Style>
</Line>
<Ellipse Fill="Red" Width="6" Height="6" Margin="-3" Canvas.Left="10"
Canvas.Top="{Binding CurrentPrice, Converter={StaticResource PConverter}}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer
VerticalScrollBarVisibility="Hidden"
Background="{TemplateBinding Panel.Background}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
oh! I forgot to add those ValueConverters, here're those:
public class VolumeConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var height = (double)values[0];
var higest = (double)values[1];
var value = (double)values[2];
return value * height / higest;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class PriceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is double)) return null;
var price = (double)value;
var remainingHeight = 90;
var priceRange = 30 - 20.0;
return 45 + ((price - 20) * remainingHeight / priceRange);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and here's how it looks like:
As suggested by #Clemens, I've to have another double?, in case where Insert(0, ... ) is used on ObservableCollection instead of Add(...) to add the last item in first place and removed the AternationCount/Index stuff.
The following example uses a vertically flipped Canvas to invert the y-axis order, so that it goes upwards. So PConverter should return positive y values.
Besides the Rectangle and Ellipse elements it draws a Line element from the previous data value to the current one by means of RelativeSource={RelativeSource PreviousData} in the value binding. It also uses a DataTrigger on the AlternationIndex to hide the first line.
<ItemsControl ... AlternationCount="2147483647">
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Width="20">
<Canvas.LayoutTransform>
<ScaleTransform ScaleY="-1"/>
</Canvas.LayoutTransform>
<Rectangle Fill="LightGray" Margin="1" Width="18"
Height="{Binding Value1, Converter={StaticResource PConverter}}"/>
<Line Stroke="DarkGreen" StrokeThickness="3"
X1="-10" X2="10"
Y1="{Binding Price,
Converter={StaticResource PConverter},
RelativeSource={RelativeSource PreviousData}}"
Y2="{Binding Price,
Converter={StaticResource PConverter}}">
<Line.Style>
<Style TargetType="Line">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource
AncestorType=ContentPresenter}}"
Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Line.Style>
</Line>
<Ellipse Fill="Red" Width="6" Height="6" Margin="-3" Canvas.Left="10"
Canvas.Top="{Binding Price, Converter={StaticResource PConverter}}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
Since the value converter is now also called for a non-existing value (for PreviousData of the first item), you have to make sure that it checks if the passed value is actually a double:
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is double)) return 0d;
...
}

WPF showing control with DataTrigger skips it in TabOrder

I would like to show 'additional details' Textbox when the previous TextBox's value is not 0.00 (zero). This is easily accomplished with DataTriggers:
<TextBox Name="FirstTB" Text="{Binding Amount, StringFormat=F2}"/><!--when this TB is not 0-->
<TextBox Name="SecondTB"> <!--show this TB-->
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Amount}" Value="0">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Name="ThirdTB"/>
The issue is, however, when changing value of FirstTB to <> 0 and pressing Tab the focus jumps to the ThirdTB instead of SecondTB (even though SecondTB now is Visible due to the DataTrigger). How can I fix this issue?
Sadly, UpdateSourceTrigger=PropertyChanged does not appear to be an option due to its interference with StringFormats - it gives terrible UX when you are editing the value, carret jumps around like crazy due to constant StringFormat evaluation. Viewmodel used in example above:
public class MyVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double _amount;
public double Amount
{
get { return _amount; }
set
{
_amount = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Amount)));
}
}
}
Maybe a stupid work around. But this will do the work:
View:
<TextBox Name="SecondTB" IsVisibleChanged="SecondTB_OnIsVisibleChanged">
<!--show this TB-->
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Amount, Mode=OneWay}" Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
C#:
private void SecondTB_OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (((bool) e.NewValue))
{
if(!(sender is TextBox txbx)) return;
ThirdTB.GotFocus += ThirdTbOnGotFocus;
}
}
private void ThirdTbOnGotFocus(object sender, RoutedEventArgs e)
{
SecondTB.Focus();
ThirdTB.GotFocus -= ThirdTbOnGotFocus;
}
If you use an IValueConverter you get a very elegant way for your problem. Everytime the value is changed, the logic is called.
public class IsZeroToHiddenConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double d && d == 0)
return Visibility.Hidden;
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You calso play around and add ConverterParameter to decide if you want Collapsed or Hidden behavior.
Usage
<Grid>
<Grid.Resources>
<local:IsZeroToHiddenConverter x:Key="IsZeroToHiddenConverter"/>
</Grid.Resources>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="Tb1" Grid.Row="0" Width="100" Margin="5" Text="{Binding Path=Amount, StringFormat=F2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="Tb2" Grid.Row="1" Width="100" Margin="5" Visibility="{Binding Path=Amount, Converter={StaticResource IsZeroToHiddenConverter}}"/>
<TextBox x:Name="Tb3" Grid.Row="2" Width="100" Margin="5"/>
</Grid>
</Grid>
Preview

WPF Change icon depending on binding in Control Template

I have a control template that I've defined for a DevExpress TextEdit control and I want to change the Image Source property in the template depending on a binding (e.g. IsIncrease).
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
If the property IsIncrease was set to true then one particular icon should be shown and if the property was set to false then another particular icon should be shown. Anyone know how to do this?
Thanks
You can do it using a converter, you have two choices, either you include 2 Images inside your Grid and you hide/show them using the converter or you use one Image and use the converter to change the Source property (which is probably a better solution). Here's a converter that can handle both situation, followed by both solutions.
BoolConverter :
public class BoolConverter : MarkupExtension, IValueConverter
{
public object TrueValue { get; set; } = Binding.DoNothing;
public object FalseValue { get; set; } = Binding.DoNothing;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is bool))
return Binding.DoNothing;
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == TrueValue)
return true;
if (value == FalseValue)
return false;
return Binding.DoNothing;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
MainWindow.xaml (solution 1 : two Images) :
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"
Visibility="{Binding IsIncrease, Converter={local:BoolConverter TrueValue=Collapsed, FalseValue=Visible}}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/TrafficLights3_1.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"
Visibility="{Binding IsIncrease, Converter={local:BoolConverter TrueValue=Visible, FalseValue=Collapsed}}"/>
</Grid>
</ControlTemplate>
MainWindow.xaml (solution 2 : one Image) :
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="{Binding IsIncrease,
Converter={local:BoolConverter TrueValue='pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png',
FalseValue='pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/TrafficLights3_1.png'}}"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
To achieve that you need 2 properties to store source for image.I'm gonna use Tag property and write an attached property to store 2 image source and use trigger to change the source
public class AttachedProperty
{
public static readonly DependencyProperty AltSourceProperty =
DependencyProperty.RegisterAttached("AltSource",
typeof(string), typeof(AttachedProperty),
new PropertyMetadata());
public static string GetAltSource(DependencyObject obj)
{
return (string)obj.GetValue(AltSourceProperty);
}
public static void SetAltSource(DependencyObject obj, string value)
{
obj.SetValue(AltSourceProperty, value);
}
}
ControlTemplate
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}" />
<Image x:Name="image"
Width="17"
Height="16"
Margin="0,0,5,0"
HorizontalAlignment="Right"
RenderOptions.BitmapScalingMode="NearestNeighbor" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsIncrease " Value="True">
<Setter TargetName="image" Property="Source" Value="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsIncrease " Value="False">
<Setter TargetName="image" Property="Source" Value="{Binding Path=(local:AttachedProperty.AltSource), RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<dxe:TextEdit Tag="Image1.jpg" local:AttachedProperty.AltSource="Image2.jpg"/>

Combine design time menu elements into databound menu item binding

Hopefully I got the vernacular right on my question so I don't throw people completely off.
I have a menu that is data bound and using a HierarchicalDataTemplate that handles the various nested types in my binding object. So far everything is working fantastically; but now I would like to add a couple additional menu items to menu items of a certain type, but of course that breaks the binding as I cannot bind to a collection that already contains elements. CompositeCollection seems to be what I am looking for but I keep running into syntax errors when trying to apply that to my HierarchicalDataTemplate.
<Menu.Resources>
<HierarchicalDataTemplate DataType="{x:Type ODIF:PluginContainer}" ItemsSource="{Binding Instance.Devices}">
<HierarchicalDataTemplate.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Instance.Devices}"/>
<MenuItem>One more item!</MenuItem>
<MenuItem>Two more items!</MenuItem>
</CompositeCollection>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Image Source="{Binding PluginIcon}" Width="16" Height="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding PluginIcon}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding PluginName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type ODIF:Device}" ItemsSource="{Binding InputChannels}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Image Source="{Binding StatusIcon}" Width="16" Height="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusIcon}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding DeviceName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type ODIF:DeviceChannel}">
<local:ChannelBox Channel="{Binding}" Width="200" Click="ChannelClicked"/>
</HierarchicalDataTemplate>
</Menu.Resources>
This throws:
The specified value cannot be assigned. The following type was
expected: "BindingBase".
and
Property 'ItemsSource' does not support values of type 'CompositeCollection'.
I guess you have to use a converter for solving your issue.
Let's suppose that MenuModel is a class which represents a menu item. It is really simple:
public class MenuModel
{
private List<MenuModel> children = new List<MenuModel>();
public string Description { get; set; }
public IList Children
{
get
{
return children;
}
}
}
Now we have our XAML:
<Window.Resources>
<collections:ArrayList x:Key="someOtherMenus">
<local:MenuModel Description="Menu A">
<local:MenuModel.Children>
<local:MenuModel Description="SubMenu i" />
<local:MenuModel Description="SubMenu ii" />
</local:MenuModel.Children>
</local:MenuModel>
<local:MenuModel Description="Menu B" />
</collections:ArrayList>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top" Margin="3" ItemsSource="{Binding MenuModels}">
<Menu.ItemTemplate>
<HierarchicalDataTemplate>
<HierarchicalDataTemplate.ItemsSource>
<Binding ConverterParameter="someOtherMenus">
<Binding.Converter>
<local:CompositeCollectionConverter />
</Binding.Converter>
</Binding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding Description}" Margin="3" />
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<TextBlock Text="text" Margin="10" />
</DockPanel>
So now we can consider the converter implementation:
public class CompositeCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
MenuModel menuModel = value as MenuModel;
if (parameter != null)
{
CollectionContainer collectionContainer = new CollectionContainer();
collectionContainer.Collection = menuModel.Children;
CompositeCollection compositeCollection = new CompositeCollection();
compositeCollection.Add(collectionContainer);
collectionContainer = new CollectionContainer();
collectionContainer.Collection = (IEnumerable)App.Current.MainWindow.FindResource(parameter);
compositeCollection.Add(collectionContainer);
return compositeCollection;
}
else
{
return menuModel.Children;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
As you can see it uses its parameter to get a specific resource (in our case I am using the resource called "someOtherMenus", which is an IEnumerable of MenuModels).
Of course the HierarchicalDataTemplate is recursive so the "someOtherMenus" MenuModels will be added to each level (but the first one) of your Menu.
I hope my sample can help you.

Resources