How to use ElementName when binding to UIElement within template? - c#

I am trying to figure out how to get around the issue of using ElementName when binding to a listbox that is in the same template as the item I am trying to bind. When I run the code shown below, I get the following error:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
commandParameter was null.
Additionally, there is no indication that there is any binding error in the Debug output window.
I have tried using the x:Reference method suggested in Binding ElementName inside a DataTemplate but this threw the following error:
System.Windows.Markup.XamlParseException: 'Cannot call MarkupExtension.ProvideValue because of a cyclical dependency. Properties inside a MarkupExtension cannot reference objects that reference the result of the MarkupExtension
My code is shown below:
<ContentControl Grid.Row="0" Grid.RowSpan="2" HorizontalAlignment="Center" Width="Auto"
Height="Auto" VerticalAlignment="Top" Name="AddDeviceContentControl">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ChooseNewDevice}" Value="true">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<ListBox Name="AddDeviceList" ItemsSource="{Binding Source={StaticResource DeviceTypes}}" >
<ie:Interaction.Triggers>
<ie:EventTrigger EventName="SelectionChanged">
<ie:InvokeCommandAction
Command="{Binding AddDeviceCommand}"
CommandParameter="{Binding ElementName=AddDeviceList, Path=SelectedItem}"/>
</ie:EventTrigger>
</ie:Interaction.Triggers>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Note: I am using a content control and template here because I want the list to be invisible unless the ChooseNewDevice bool is true. I chose not to use a popup because I want the listbox SelectedItem to clear when the list is closed, but the popup saves the state. If there is some way to clear the listbox SelectedItem in XAML (in order to follow MVVM) that would also solve my problem.

As suggested by other people here, you should replace your Interaction Trigger in XAML by moving that logic on your ViewModel, triggered by SelectedItem property changed.
Your XAML would become simplier, something like this:
<ListBox ItemsSource="{Binding Source={StaticResource DeviceTypes}}"
SelectedItem="{Binding SelectedItem}">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ChooseNewDevice}" Value="true">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>

Related

WPF: InvalidCastException: Unable to cast object of type 'System.Windows.Style' to type 'System.Windows.ResourceDictionary'

I try to re-style TextBoxes in my Control to have the same look for ReadOnly as if they were Disabled.
Last week I could achieve this without any problem like this:
<UserControl.Resources>
<Style TargetType="{x:Type Border}" x:Key="TextBoxBorderStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TextBoxBase}}" Value="True">
<Setter Property="Opacity" Value="0.56"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
... and use it in TextBoxes like this:
<TextBox Text="{Binding MyText}"
IsReadOnly="{Binding ReadOnly}"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled" >
<TextBox.Resources>
<StaticResource ResourceKey="TextBoxBorderStyle" />
</TextBox.Resources>
</TextBox>
I don't remember changing anything in this control in the meantime. (Though I updated Visual Studio 2019 to 16.9.4 today). But when I run my application now, it gives a run time error:
System.Windows.Markup.XamlParseException HResult=0x80131501
Message='Set property 'System.Windows.FrameworkElement.Resources'
threw an exception.' Line number '90' and line position '33'.
Source=PresentationFramework StackTrace: at
System.Windows.Markup.XamlReader.RewrapException(Exception e,
IXamlLineInfo lineInfo, Uri baseUri)
This exception was originally thrown at this call stack:
System.Windows.Baml2006.WpfSharedBamlSchemaContext.Create_BamlProperty_FrameworkElement_Resources.AnonymousMethod__276_0(object,
object)
MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(object, System.Xaml.XamlMember, object)
Inner Exception 1: InvalidCastException: Unable to cast object of type
'System.Windows.Style' to type 'System.Windows.ResourceDictionary'.
It works as it should when I copy-paste the style directly into all of the Textbox.Resources .
Anyone knows why?
Especially since sharing the StaticResource has worked without a problem before.
EDIT: It works as expected in the xaml editor/designer and properly adjusts the style to the view model data without any exception. Only at run time there is a problem.
You should either replace <StaticResource ResourceKey="TextBoxBorderStyle" /> with an actual Style:
<TextBox Text="{Binding MyText}"
IsReadOnly="{Binding ReadOnly}"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled" >
<TextBox.Resources>
<Style TargetType="Border" BasedOn="{StaticResource TextBoxBorderStyle}" />
</TextBox.Resources>
</TextBox>
...or change the TargetType of your Style and simply set the Style property of the TextBox:
<UserControl.Resources>
<Style TargetType="TextBox" x:Key="TextBoxBorderStyle">
<Style.Resources>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TextBoxBase}}" Value="True">
<Setter Property="Opacity" Value="0.56"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
</UserControl.Resources>
Usage:
<TextBox Text="{Binding MyText}"
IsReadOnly="{Binding ReadOnly}"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled"
Style="{StaticResource TextBoxBorderStyle}" />
Replace
<TextBox Text="{Binding MyText}"
IsReadOnly="{Binding ReadOnly}"
VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled" >
<TextBox.Resources>
<StaticResource ResourceKey="TextBoxBorderStyle" />
</TextBox.Resources>
</TextBox>
With
<TextBox Text="{Binding MyText}"
IsReadOnly="{Binding ReadOnly}"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled"
Style={DynamicResource TextBoxBorderStyle}/>
That should fix the issue. If not, try to reset your resource dictionary by deleting the old style and making a new one by going to that textbox and right-click - Edit Style/Template - Edit a Copy
And then select "Resource Dictionary" and choose your resource dictionary

XAML/C# Hiding a TextBlock if the bound value is empty

in my WPF application I have a listview that only appears if a bound item has an values, this works like so
<ListView Grid.Row="1" Grid.Column="1" Margin="0,5,0,20" BorderThickness="0"
ItemContainerStyle="{StaticResource SelectionlessListViewItemStyle}"
ItemsSource="{Binding MissingAssets}">
<ListView.Style>
<Style TargetType="ListView">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
So this will only display the listview if MissingAssets has any values and works fine, above that I wanted a textblock as a header to just say "The following assets could not be found", and I want to hide this text of course if this listview is hidden too, I tried implementing it like this
<TextBlock Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" FontWeight="Bold" Text="The following assets could not be found">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding MissingAssets}" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
But for some reason it won't hide even if MissingAssets is empty, I've tried using several different things in Value="" but nothing gets it to work. Is there a property or something I'm forgetting to set?
Thanks
You have many options here. The simplest should be to bind the TextBlock.Visibility to the ListView.Visibilty:
<StackPanel>
<TextBlock Visibility="{Binding Elementname="MissingAssetsListView", Path="Visbibility" />
<ListView x:Name="MissingAssetsListView" />
</StackPanel>
Found a workaround, I created a new string property in my C# code that remains blank unless missing assents are found, if they are found I populate the string and bind that string to a label in my XAML, so if the string is empty then there will be no visible label on the UI

WPF DataTemplate Binding depending on the type of a property

I have a collection of objects bound to a hierarchical data template, each of my objects have a property on them (lets call it Property "A") that is of a certain type. This type varies among each of the objects.
If the data template contains an image and some text, what would be the best way to change the image that is displayed in the template based on the type of property "A".
I know I could just stick this into a converter and do the binding translation manually in code, but with all the binding facilities available in WPF, I think theres probably a better way.
It's pretty simple to do this within your data template, if you create local data templates and use a ContentPresenter. This template presents objects of type MyObject, displaying an image whose source is determined by the type of the A property next to a TextBlock that displays the content of the Text property:
<DataTemplate DataType="{x:Type MyObject}">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type Thing1}">
<Image Source="thing1.png"/>
</DataTemplate>
<DataTemplate DataType="{x:Type Thing2}">
<Image Source="thing2.png"/>
</DataTemplate>
</StackPanel.Resources>
<ContentPresenter Content="{Binding A}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
If you want to use styles to do this instead, you're going to run into a problem, because data triggers want to look at property values, and the type of the A property is not, itself, exposed as a property.
Unless, of course, you implement one:
public Type AType { get { return A.GetType(); } }
(You'll also need to raise PropertyChanged for AType when the value of A changes.) Once you've done this, you should be able to implement a data trigger in a style, e.g.:
<Style TargetType="Image">
<Setter Property="Source" Value="default.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding AType}" Value="{x:Type Thing1}">
<Setter Property="Source" Value="thing1.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding AType}" Value="{x:Type Thing2}">
<Setter Property="Source" Value="thing2.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
I think You can do that with triggers.
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding TheProperty}" Value="TheValue">
<Setter Property="Source" Value="NewPath"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
DataTemplateSelector doesn't seem to be a good choice here since you have the same template for all values of A.
Use DataTriggers:
<DataTemplate>
<StackPanel>
<Image x:Name="image" />
<TextBlock>Your text</TextBlock>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=A}" Value="ValueToCheck1">
<DataTrigger.Setters>
<Setter Property="Source" Value="Image1.png" TargetName="image" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=A}" Value="ValueToCheck2">
<DataTrigger.Setters>
<Setter Property="Source" Value="Image2.png" TargetName="image" />
</DataTrigger.Setters>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Haven't tested it, but the idea is like that.

Setting ItemTemplate based on CheckBox value

I have a DataTemplate which contains a CheckBox and ListBox. When the CheckBox is checked, I want to change the ItemTemplate property on the ListBox to change the appearance of each item.
Right now, it looks like this:
<DataTemplate DataType={x:Type MyViewModel}>
<DockPanel>
<CheckBox DockPanel.Dock="Bottom"
Content="Show Details"
HorizontalAlignment="Right"
IsChecked="{Binding ShowDetails}"
Margin="0 5 10 5" />
<ListBox ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource SimpleItemTemplate}"
Margin="10 0 10 5">
<ListBox.Triggers>
<DataTrigger Binding="{Binding ShowDetails}" Value="True">
<Setter Property="ItemTemplate"
Value="{StaticResource DetailedItemTemplate}" />
</DataTrigger>
</ListBox.Triggers>
</ListBox>
</DockPanel>
</DataTemplate>
However, when I try to compile, I get the following error messages:
Value 'ItemTemplate' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.
and
Cannot find the static member 'ItemTemplateProperty' on the type 'ContentPresenter'.
I'm still fairly new to WPF, so perhaps there is something I'm not quite understanding?
You need to do this through the ListBox Style rather than directly through its Triggers collection. A FrameworkElement's Triggers collection can only contain EventTriggers (so I'm surprised your sample got as far as complaining about the properties!). Here's what you need to do:
<ListBox ItemsSource="{Binding Items}">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="ItemTemplate" Value="{StaticResource SimpleItemTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShowDetails}" Value="True">
<Setter Property="ItemTemplate"
Value="{StaticResource DetailedItemTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>

Relative binding in style of control inside ListBoxItem template

My problem is with the following code, with binding the IsAvailable property of the MyListBoxItem class. My current solution:
<ListBox ItemTemplate="{StaticResource myTemplate}">
<ListBox.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type local:MyListBoxItem}">
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
... (more datatemplates)
</ListBox.Resources>
</ListBox>
My question: In my solution the value of IsAvailable "goes through" two bindings. The first one binds the value to the Tag property of the Label and then in the style triggers, a trigger checks its value and sets a property of the Label. When I used Binding="{Binding IsAvailable, RelativeSource={RelativeSource AncestorType={x:Type local:MyListBoxItem}}}" it didn't work, because the Style can't see any ancestor of the Label (or something similar reason), it resulted binding errors (with code 4 or 40 maybe), for each item added to the ListBox.
So finally: can I make the solution more simple, or there is no another (better) one?
An important thing I've forgot to mention, sorry: I put the DataTemplate in the ListBox's resources because I have more templates (they are basically differ, so I can't style them with triggers), which I have to switch between sometimes...
The ItemTemplate will take the type that the ItemsSource is bound to. Therefore you should be able to simply bind to IsAvailable, as the ListBox's item type is MyListBoxItem. Try this:
<ListBox ItemsSource="...">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Foreground="Green" Content="{Binding Title}" Tag="{Binding IsAvailable}">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You'll need to set your ItemsSource property to a Binding to the MyListBoxItem collection.

Categories

Resources