Setting the DataContext of a control after a data triggered is fired - c#

I have a view which contains a ContentControl which has its ContentTemplate changed dynamically depending on a boolean property within the view model using a data trigger.
<ContentControl>
<!-- MyFirstControl user control by default-->
<local:MyFirstControl/>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Different user control when trigger fired-->
<local:MySecondControl />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
The MySecondControl user control, which displays when the trigger is fired is intended to display a textblock, of which its Text property binds to a property also within the same view model.
I am obviously wrong, but my thinking was that the triggered control would inherit the same data context. Instead it is trying to use the MyFirstControl user control as its data context (I also receive this error: System.Windows.Data Error: 40 : BindingExpression path error:).
I have tried to explicitly state the data context of the triggered control with:
<local:MySecondControl DataContext="{Binding}"/>
However it is still using the default control (MyFirstControl) as its data context.
My question is, how do I force the triggered control to use the same data context as the view file it is within?
I am fairly new to the WPF scene so I hope this makes sense!
Thanks in advance.

There's a difference between Content and ContentTemplate.
Content is your actual content for the control, while ContentTemplate defines how to draw the Content
You are setting the Content property to MyFirstControl. Your trigger is changing the ContentTemplate property, so it is changing the way your Content (MyFirstControl) gets drawn so it is drawn using MySecondControl, however the Content itself is unchanged so the DataContext will still be your MyFirstControl.
You probably want to set the default ContentTemplate to MyFirstControl instead of the actual Content property.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<!-- Set default ContentTemplate -->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:MyFirstControl />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Different user control when trigger fired-->
<local:MySecondControl />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Hope this will help:
<local:MySecondControl DataContext="{Binding Path=DataContext,
RelativeSource={RelativeSource TemplatedParent}}"/>

Related

Trouble finding correct relative binding syntax for DataGrid.ItemsSource from TabItemHeaderTemplate

I have a TabControl in my Control with in each tab a DataGrid. I am styling the tab header using a template. I would like to bind to the DataGrid.ItemsSource on the tab to be able to apply a filter to add an image to the TabItem indicating a status.
Below is my VisualTree with an arrow indicating the binding:
And this is my template with the relative binding, which unfortunately is not working.
<DataTemplate x:Key="TabItemHeaderWithIconTemplate">
<StackPanel Orientation="Horizontal">
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger
Value="True"
Binding="{Binding Path=ItemsSource, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Converter={StaticResource StatusHasFailureConverter}, Mode=OneWay}">
<Setter Property="Source" Value="/REDACTED;component/Resources/red-x-icon.png"/>
</DataTrigger>
<DataTrigger
Value="False"
Binding="{Binding Path=ItemsSource, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Converter={StaticResource StatusHasFailureConverter}, Mode=OneWay}">
<Setter Property="Source" Value="/REDACTED;component/Resources/green-checkmark-icon.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
If I remove the relative binding and directly bind to a CollecionViewSource defined in my resources the StatusHasFailureConverter is triggered and the icon is visible and matches the output of my converter, but with my relative binding no icon appears at all, the converter seems not to be triggered, but also no binding issues are raised.
Any pointers on how to define the binding to point to the topmost DataGrid ItemsSource in the TabControl (indicated with the arrow)?
It seems because of the way TabControl works, the contents of tabs are not children of their items (only the headers are children of the individual items). This means a relative binding would not be possible as far as I can tell. I would instead recommend binding the DataGrid and TabItem header to he same common source, which it seems you've already figured out how to do.

How to use ElementName when binding to UIElement within template?

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>

Force focus on control via xaml style

I have a treeview and a listbox.
I want to specify on the style trigger-setter option that, when the listbox visibility is Hidden the focus have to return on the treeview.
Is it possibile to condition an user control focus on the state of another user control?
something like
<Style TargetType="{x:Type TreeView}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Hidden">
<Setter Property="IsFocus" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
Yes it is, see the example here
FOR CHILDREN IN CONTAINERS
<StackPanel FocusManager.FocusedElement="{Binding ElementName=lol}">
<TextBox x:Name="lol"/>
<TextBox x:Name="lul"/>
</StackPanel>
FOR SELF
<TextBox FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>

C# WPF - Change control content when another control event occurs

I'm quite new to WPF and MVVM and I'm trying to create a custom WindowChrome with all the standard Window features. I'm struggling with the Maximize/Minimize window Button content: I want the content to change when the user double clicks the WindowChrome bar, in order to show the right icon:
When I double click the bar, the result should be:
I managed to change the content with the Button Triggers, but how can I change it when another control event occurs?
Thanks in advance for the help!
Give the Button a Style with triggers that set the content based on the value of Window.WindowState. This isn't an event. The button reflects the current state of the window.
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger
Binding="{Binding WindowState, RelativeSource={RelativeSource AncestorType=Window}}"
Value="Maximized">
<Setter Property="Content">
<Setter.Value>
<!-- I don't know if you're using a Path or what -->
<Path Stroke="White" Data="..." />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger
Binding="{Binding WindowState, RelativeSource={RelativeSource AncestorType=Window}}"
Value="Normal">
<Setter Property="Content">
<Setter.Value>
<!-- I don't know if you're using a Path or what -->
<Path Stroke="White" Data="..." />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
It would be wiser to set the Template of the button instead, because there’s only one copy of each of those Paths, and if you reuse the style twice, they can’t be shared.
If there's some reason why this won't work with your code, show me your code.

Loading UserControl to the region of the Window on ComboBox change

I have got ComboBox which is populated with collection of customTypes. On comboBox change, I would like to load/change content in the particular region so that it loads related data for the selected ComboBox item (it could be in the form of loading userControl or I i dont mind specifying DataTemplate for it).
It is similar to this question WPF Load Control question. But in this question, he is talking about individual DataTemplates in the actual Listbox, while I am talking about populating certain window region on ComboBox change.
I am using MVVM (and not PRISM) .Net 3.5.
U could use ContentControl which is the placeholder for the actual Content that is dynamically decided as per combobox selection.
The follwoing code is just for guidance
<Window ...>
<Window.Resources>
<MyView1 x:Key="MyView1" />
<MyView2 x:Key="MyView2" />
</Window.Resources>
...
<ComboBox x:Name="MyCombo">
<ComboBox.ItemsSource>
<sys:String>"MyView1"</sys:String>
<sys:String>"MyView2"</sys:String>
....
</ComboBox.ItemsSource>
</ComboBox>
...
<!-- This is where your region is loaded -->
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=MyCombo}"
Value="MyView1">
<Setter Property="Content"
Value="{StaticResource MyView1}"
</DataTrigger>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=MyCombo}"
Value="MyView2">
<Setter Property="Content"
Value="{StaticResource MyView2}"
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Window>
The data loading can be part of the MyView1 and MyView2 user control's constructor or your main UI's data context view model.
As far as I understand the question is how to change underlying data being bound to UI and not a DataTemplate only.
You can use EventSetter which will be handled in code behind where you can switch DataContext for a region you've mentioned:
<ComboBox>
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<EventSetter Event="Selector.SelectionChanged"
Handler="YourHandler"/>
</Style>
</ComboBox.Resources>
</ComboBox>
But from MVVM perspectives it could be not perfect solution so you can introduce your own ComboBox class wich is Command aware, see this SO post: WPF command support in ComboBox
In this way you can decouple logic from UI using Command.

Categories

Resources