DataTrigger Binding to a ToggleButton within another namespace - wpf

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.

Related

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>

Can't change content-template dynamically

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?

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>

FrameworkElement`s DataContext Property does NOT inherit down the element tree

Hello WPF Pros at least I hope some of you read this!
DataContext is a property on FrameworkElement (base class for all WPF Controls) and is implemented as a DependencyProperty. That means all the descendant elements in the logical tree share the same DataContext.
So the ContentControl should do it with its descendant elements right?
I have a scenario where that is NOT the case and I would like to know WHAT is the cause of that misbehaviour ?!
That you understand a bit more about it please read this thread ( dont NOT want to copy everything here) where the trouble starts...:
WPF: Can not find the Trigger target 'cc'. The target must appear before any Setters, Triggers
and to say it in short words: My DataTemplates within the ContentControl do have a dead DataContext that means there is NOTHING to bind to it, what is actually not possible...
Every Element down the ContentControl has NOTHING set in the DataContext Property ???
DataContext is a property on
FrameworkElement (base class for all
WPF Controls) and is implemented as a
DependencyProperty. That means all the
descendant elements in the logical
tree share the same DataContext.
The fact that it's a dependency property doesn't imply inheritance... It's true for DataContext, but only because the dependency property has the FrameworkPropertyMetadataOptions.Inherits flag in its metadata.
So the ContentControl should do it
with its descendant elements right?
ContentControl is a bit special: the DataContext of its descendants (the visual tree built from the DataTemplate) is actually be the Content of the ContentControl. So if your ContentControl has no content, the DataContext inside it is null.
This worked for me:
<ContentControl ContentTemplate="{StaticResource NotesTemplate}"
Content="{Binding}"
DataContext="{Binding HeightField}"/>
Without the Content="{Binding}", the DataContext was NULL
The last answer (from VinceF) worked for me too.
I wanted to show a usercontrol depending on the value of a property in my viewmodel. So I made a ContentControl with some Style Triggers. Depending on the value of a bind property the trigger sets a specific ContentTemplate containing the specific usercontrol.
The usercontrol was shown right, but its DataContext was always null. So I had to set the Context of the ContentControl to: Content="{Binding}" After that, the UserControls worked fine and had the same DataContext as their parent.
So my XAML looks like that:
In the Resources part I defined two DataTemplates; each one for each UserControl I want to show.
<DataTemplate x:Key="ViewA">
<namespace:UserControlA/>
</DataTemplate>
<DataTemplate x:Key="ViewB">
<namespace:UserControlB/>
</DataTemplate>
The part where I show the UserControl depending on a property is the following:
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Property}" Value="0">
<Setter Property="ContentControl.ContentTemplate" Value="{StaticResource ViewA}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Property}" Value="1">
<Setter Property="ContentControl.ContentTemplate" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
after reading this question and previous answers, I prefer using ContentControl with data triggered Content like this:
Controls which will be set as Content of ContentControl:
<TextBox x:Key="ViewA">
...
</TextBox>
<ComboBox x:Key="ViewB">
...
</ComboBox>
ContentControl which switch own content by DataTrigger in ContentControl style:
<ContentControl>
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Property}" Value="0">
<Setter Property="Content" Value="{StaticResource ViewA}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Property}" Value="1">
<Setter Property="Content" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I hope this helps to someone like previous answers to me.

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