Can't change content-template dynamically - wpf

I have a ContentControl that I need to set it's ContentTemplate dynamically.
so I decided to write 2 DataTemplates, and then style my ContentControl such that a trigger fires and set the proper template (dt1/dt2) when a Boolean dependency property in my view-model changes (true/false).
But the problem is if the Boolean property is primarily set to true, the data template will always be dt1 and changing the property to false wont change the template to dt2.
since the data triggers are bound to the Boolean dependency property, shouldn't changing the property result in firing the triggers?
notes:
There is a button in MyView which changes BooleanDependencyProp on it's
click event.
MyViewModel inherits from an interface that
implements INotifyPropertyChanged.
Xaml:
<UserControl x:Class="Views.MyView">
...
<StackPanel>
<ContentControl Content="{Binding RelativeSource={RelativeSource AncestorType=MyView}, Path=MyViewModel}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding BooleanDependencyProp}" Value="true">
<Setter Property="ContentTemplate">
<Setter.Value>
<dt1 ... />
</Setter.Value>
</Setter>
<DataTrigger Binding="{Binding BooleanDependencyProp}" Value="false">
<Setter Property="ContentTemplate">
<Setter.Value>
<dt2 ... />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</StackPanel>

This is a known shortcoming of the WPF framework, if you want to apply different data templates, consider either using the visual state manager to change the presentation, or swap out the content data template for a user control that changes based on the triggers instead, you'll get more mileage.
There's a lot more I could say, but it would involve knowing your scenario and the differences in these DataTemplates, why you are disambiguating, etc. Also, MVVM all around? or straight ahead Code+Markup style with a few view models?

Related

DataTrigger Binding to a ToggleButton within another namespace

I try to access a ToggleButton that is within a separate UserControl to Trigger a DockPanel.Style DataTrigger.
Here is how I made it work when both, the ToggleButton and the DockPanel, are in the same namespace:
<ToggleButton x:Name="OneToggleButton"
Content="Click me..." />
<DockPanel>
<DockPanel.Style>
<Style>
<Setter Property="UIElement.Visibility"
Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked,
ElementName=DetailsBookToggleButton}"
Value="False">
<Setter Property="UIElement.Visibility"
Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
<TextBlock DockPanel.Dock="Top" Text="..." />
</DockPanel>
But now when I move the ToggleButton into an other file (other namespace) it doesn't work anymore. ElementName (as I understand it) only works for elements within the same file.
So how can I manage a Binding to the IsChecked of my ToggleButton in another file?
Anybody have a suggestion? Would be great :)
FYI, the term you are looking for is "name scope", and there is no way to reference an element defined in another name scope. Arguably, you should not be allowed to do this.
Rather than binding one UI element to another, consider binding them both to a common property, either in your view model, on some common ancestor element, or via dependency property inheritance.

WPF MVVM Light - About views communicating

I am currently working on a project for work. I am seeking an outside design opinion, as well as some general information on the issue I am faced with.
We have a MainWindow.xaml file that is located in the root directory of the project. In this main window is some design and logic for some collapsing stack panels, ribbon toolbar, etc.
So far the idea is to include a different in each stack panel to help make the code neat. The views are located in a 'Views' folder. So just to be clear, the MainWindow.xaml and other views ARE NOT in the same directory. This is open to change, if necessary.
So here is my question/issue: We have a Window ('A'), a main panel with a collapsable stack panel with some information ('B') that is contained in window 'A'. Then there is another stack panel to manage the contents in 'B', (collapse/visisble) ('C').
'A' contains a toggle button to show/collapse 'B'.
'B' contains a button to show/collapse 'C'.
'C' contains a button to show/collapse itself, 'C'.
'C' should have its logic all contain within a view, so the MainWindow ('A') should have a simple tag:
<StackPanel Style="{StaticResource FrameGradient}" Tag="{Binding ElementName=ToggleButton}">
<view:Content></view:Content>
</StackPanel>
Currently, the bindings for toggling the buttons within 'A' are in the styling. The In this case FrameGradient has triggers like so:
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
//Setter properties
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
Is it possible to, within the 'Content' View to TOGGLE the panel, 'C', which is NOT within the view? I feel like I am missing a core idea of XAML here. I found a 'cheap' work around which is to place the 'Close' button from the Content View outside of the tags, but then that leads to styling issues and I feel like I shouldn't have to do something silly like that. Again, the idea is that the toggle button for Stack Panel 'C' is contained within another view and I want to be able to toggle it from another view.
I apologize if I am not clear enough, I will provide whoever asks with more information if required here.
UPDATE
I have some time to actually add the code I am using so that this might make more sense.
MainWindow.xaml - Logic for Filter panel (Located in root)
<StackPanel Grid.Row="1" Grid.Column="4" Visibility="Collapsed" Style="{StaticResource FrameGradient}">
<Grid x:Name="FilterContentGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<view:Filters></view:Filters>
</Grid>
</StackPanel>
Filters.xaml - Logic for Filters view (Located in /Views)
The button within the file that needs to Collapse the above StackPanel.
<Button x:Name="FilterManagementCloseButton" Content="CLOSE"></Button>
Theme.Xaml - Logic for all styling (Located in root, along with MainWindow.xaml and App.xaml)
Button Styling
<Style x:Key="FilterManagementCloseButton" TargetType="Button">
<Setter Property="Padding" Value="10,5,20,3" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource AncestorType={x:Type Local:MainWindow}}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
And finally, the FrameGradient Styling also located in Theme.xaml
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Tag.IsChecked, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
SO, I hope this makes things more clear. I want the CLOSE button within Filters.xaml to COLLAPSE the stackpanel that is located in MainWindow. I realize this code is a mess at the moment.
Is it possible to, within the 'Content' View to TOGGLE the panel, 'C',
which is NOT within the view?
Create a shared VM which each other VM will have a property for which it can access. This VM can be loaded with during initialization of the other VMs. To allow for changes to happen put INotifyProperty(ies) on the shared VM which will then flag the desired logic across all views. Finally bind the target control(s) to your datacontext as normal except sub path into the shared VM target's property.
Hence when one view toggles (two way binding) a shared property it is reflected on the view of the target panel.
Update Example
The idea here is that one creates a viewmodel for the AppPage. That VM will hold generic flags which are shared across all viewmodels. Each subsequently created ViewModel will have a reference to the AppPage's viewmodel.
The example below is a mainpage where the AppVM contains a flag which informs the mainpage whether a login is in process. If it is and that value is true then a bound button on the mainpage will be enabled.
Subsequently the mainpage can override the appvm and put a new value within that flag by a bounded checkbox that can in-directly change whether the button is enabled; thus changing the flag for all other VMs in the process.
Here is the Mainpage VM, for this example I simply create the AppVM, but it could be passed in, or gotten from a static reference elsewhere. NOTE also how I don't care when AVs (appVM) property changes; it is not required for this example (we are not binding anything to AppVM, just its properties which need to be monitored).
public class MainVM : INotifyPropertyChanged
{
public AppVM AV { get; set; }
public MainVM()
{
AV = new AppVM() { LoginInProcess = true };
}
}
Here is the AppVm
public class AppVM : INotifyPropertyChanged
{
private bool _LoginInProcess;
public bool LoginInProcess
{
get { return _LoginInProcess; }
set { _LoginInProcess = value; OnPropertyChanged(); }
}
}
Here is MainPage's Xaml where the datacontext has been set to an instance of MainVM:
<StackPanel Orientation="Vertical">
<CheckBox Content="Override"
IsChecked="{Binding AV.LoginInProcess, Mode=TwoWay}"/>
<Button Content="Login"
IsEnabled="{Binding AV.LoginInProcess}"
Width="75" />
</StackPanel>
I base the MVVM off of my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding which explains the other missing items of this example such as the mainpage's datacontext loading.
You can use RelativeSource Bindings to bind from child views to properties in parent view models. Let's say you have a ToggleButton in MainWindow.xaml that is data bound to a property named IsChecked, which is declared in the object that is data bound to the MainWindow DataContext property. You could data bind to that same property from any child view with a RelativeSource Binding, like this:
<Style x:Key="FrameGradient" TargetType="{x:Type StackPanel}">
<Setter Property="StackPanel.Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.IsChecked, RelativeSource={
RelativeSource AncestorType={x:Type Local:MainWindow}}}" Value="False">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<!-- Note that there is no need for two Triggers here -->
<!-- One Setter and one Trigger is enough -->
</Style.Triggers>
</Style>

Adding tool tip functionality to a wpf control that does not descend from FrameworkElement

I'm working on a custom class that descends from DataGridColumn. The base class for DataGridColumn is DependencyObject. As such, it does not have a Tooltip property.
I want my custom class to have a Tooltip property. Actually, I want it to also have a ToolTipTemplate property that is a DataTemplate that can be used to generate the ToolTip. How do I go about adding this functionality to my class?
Tony
Its a common misconception that DataGridColumn being a dependency object, is part of the visual tree. It is not. So even if we create an inheritable dependency property (just like DataContext or FlowDirection which automatically propagates down the visual parent to its child elements), the new property of ToolTip wont descend down to individual cells, as those cells are not the children of the data grid column.
So now that we know this, the only way left is to add a binding in the CellStyle and bind to the self Column.ToolTip property. Just because you have decided to go with ToolTipTemplate, then you could add a ContentControl and then bind to its content template.
Something like this...
<tk:DataGrid x:Name="MyDataGrid" RowHeaderWidth="15"
ItemsSource="{StaticResource MyData}"
AutoGenerateColumns="False">
<tk:DataGrid.CellStyle>
<Style TargetType="{x:Type tk:DataGridCell}">
<Setter Property="ToolTip">
<Setter.Value>
<ContentControl
ContentTemplate="{Binding Column.ToolTipTemplate,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type tk:DataGridCell}}}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger
Binding="{Binding Column.ToolTipTemplate,
RelativeSource={RelativeSource Self}}"
Value="{x:Null}">
<Setter Property="ToolTip" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</tk:DataGrid.CellStyle>
....
</tk:DataGrid>

Binding to a cutom property of the template parent

Q: How do I bind to a custom property of the template parent from a child control's style DataTrigger
I've been scratching my head over this one for a couple of days.
I have a databound TreeView which uses a Style which has a Template. The TreeView is bound to a ObservableCollection and a HierarchicalDataTemplate + DataTemplate bind to properties inside a collection item.
FontGroup -> Font(s)
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Image x:Name="ExpanderImage" Source="/Typesee;component/Resources/tree_expand.png" RenderOptions.BitmapScalingMode="NearestNeighbor" />
<ControlTemplate.Triggers>
<DataTrigger Binding="??? IsItemSelected ???" Value="True">
<Setter TargetName="ExpanderImage" Property="Source" Value="/Typesee;component/Resources/tree_collapse_selected.png" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="FontTreeViewTemplate" TargetType="{x:Type TreeViewItem}">
...
<ToggleButton x:Name="Expander" Style="{StaticResource ExpandCollapseToggleStyle}" ... />
...
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsItemSelected}" Value="True">
<!-- WORKS FINE HERE -->
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
First I tried to bind like:
Binding Path=IsItemSelected, RelativeSource={RelativeSource TemplatedParent}
Then I read that might not work so I tried (including AncestorLevel 1+3):
Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}, AncestorLevel=2
Have also tried combos with UpdateSourceTrigger=PropertyChanged and Mode=TwoWay
If this is a flawed design please suggest a way of doing this: I basically want to change the image of the expand toggle button based on whether the property IsItemSelected is true on the TreeViewItem -- any ideas?
Thanks so much for any help!
The viewmodel in all likelihood will be the DataContext, so the binding should be a RelativeSource binding with a respective path which needs to explicity target the DataContext as the new source is the RelativeSource:
{Binding DataContext.IsItemSelected,
RelativeSource={RelativeSource AncestorType=TreeViewItem}}
As noted in my comment it might be advisable to extract this logic from the ControlTemplate as this leaves its bounds. One method would be subclassing the ToggleButton and exposing a public property for the image which then can be changed via a Style.

WPF ComboBox - showing something different when no items are bound

I have a ComboBox, and i want to change its look when the ItemsSource property is null. When it is in that state, i want to show a TextPanel with the text "Retrieving data" in it, and give it a look kind of similar to the watermarked textbox.
I figure to do this i need a ControlTemplate, and a trigger. I have the ControlTemplate here:
<ControlTemplate x:Key="LoadingComboTemplate" TargetType="{x:Type ComboBox}">
<Grid>
<TextBlock x:Name="textBlock" Opacity="0.345" Text="Retrieving data..." Visibility="Hidden" />
</Grid>
<!--
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.ItemsSource" Value="0">
<Setter Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
-->
</ControlTemplate>
but my issue is how do i set up the trigger to show this when the ItemsSource property is null? I have tried a couple of different ways, and each way has given me the error message "Value 'ItemsSource' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.". My ComboBox xaml is this (including the attempted trigger):
<ComboBox Margin="112,35,80,0"
Name="MyComboBox"
Height="22.723"
VerticalAlignment="Top"
DisplayMemberPath="FriendlyName"
SelectedValuePath="Path"
TabIndex="160"
>
<Trigger>
<Condition Property="ItemsSource" Value="0" />
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</ComboBox>
now should the trigger go on the ComboBox, or on the ControlTemplate? How do i access the ComboBox's ItemsSource property? Should i even be using a trigger?
Thanks!
Try putting {x:Null} for the value of the condition instead of 0.
Also I got it working by moving the Trigger to a style and modifing it slightly, see below.
<Style TargetType="ComboBox" x:Key="LoadingComboStyle">
<Style.Triggers>
<Trigger Property="ItemsSource" Value="{x:Null}">
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<ComboBox Style="{StaticResource LoadingComboStyle}" .... >
The reason it only works in a style, is that only EventTriggers are allowed in the triggers collection directly on the Framework Element. For property triggers (like above) you need to use a style (I learn something every day).
See FrameworkElement.Triggers
Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.

Resources