I'm struggling with setting DataContext of Usercontrol created using ControlTemplate.
In the code below I'd like the DriveControl's DataContext be set to it's parent's Datacontext field (pseudcode: DriveControl.DataContext = Parent.DataContext.DriveDataContext). Tried setting it in ControlTemplate itself or in Style with no luck.
I'm trying to do it in XAML alone.
<ContentControl>
<ContentControl.Resources>
<ControlTemplate x:Key="DriveTemplate">
<controls:DriveControl/>
</ControlTemplate>
<DataTemplate DataType="controls:DriveControl">
</DataTemplate>
<ControlTemplate x:Key="TimeTemplate">
<controls:TimeControl/>
</ControlTemplate>
<ControlTemplate x:Key="ApartamentTemplate">
<controls:ApartamentControl/>
</ControlTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="ContentControl">
<!--<Setter Property="Template" Value="{StaticResource DriveTemplate}"/>-->
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentMode }" Value="Drive">
<Setter Property="Template" Value="{StaticResource DriveTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding CurrentMode }" Value="SetTime">
<Setter Property="Template" Value="{StaticResource TimeTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding CurrentMode }" Value="ChooseApartament">
<Setter Property="Template" Value="{StaticResource ApartamentTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
As suggested in a comment I was indeed overwriting DataContext in code behind. So simply putting
<controls:DriveControl DataContext="{Binding DriveDataContext}"/>
works - unless you're a dummy and forget the code you wrote earlier (to my defence - I was inspecting source of DataContext in Live Properties, but apparently "Go To Source" only works for XAML files). Lesson learned.
Related
I have a StatusCell style for DataGridCell that I would like to use in several place in my application. I would like to externalize the Style tag so that I can reuse it easily without having to duplicate the code in my XAML everywhere.
Every other source I've found has required me to bind the trigger off the property from my ViewModel. But across the application, the column might be bound to MyStatusProperty or SubObject.MyStatusProperty, etc, so I want to do this to allow me to have one style that will apply to all of these without having to specify where it's binding from.
I am able to do this with a TextBlock with the following style. This lets me bind the TextBlock to whatever I want and the style binding doesn't matter where it's coming from.
<Style x:Key="StatusLabel" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="Completed">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
and when I create a textblock that I want to use this styling, all I have to do is
<TextBlock Style="{StaticResource StatusLabel}" Text="{Binding Whatever}" />
But with a DataGridCell it doesn't let me do this
<Style x:Key="StatusCell" TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="Content" Value="Completed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
<DataGridTextColumn Header="Status" Binding="{Binding MyStatusProperty}"
CellStyle="{StaticResource StatusCell}" />
I also tried setting up the trigger like this:
<DataTrigger Binding="{Binding Content, RelativeSource={RelativeSource Self}}" Value="Reviewed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</DataTrigger>
But neither of these work. I have also tried swapping out "Content" in the last example for "Binding" and "Text"
Is there another property I can bind to in the DataGridCell that will let bind the style trigger to the contents of the cell without knowing the binding path?
As usual, I found a workaround shortly after asking. Since it's working with TextBlocks, I just have to use TemplateColumns instead of TextColumns, although I'd still prefer to be able to use TextColumns since they'd use 6 less lines of XAML.
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProperty}" Style="{StaticResource StatusCellTextBlock}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Using the style:
<Style x:Key="StatusCellTextBlock" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="Completed">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
I want to assign a new DataTrigger programmatically (in code/not xaml) to every "TreeViewItem" style like I do in the following xaml.
I did some code (under xaml) where I defined my trigger but "ItemContainerStyle" is null. The function is called on Window Initialize event.
Anybody has an idea what I'm doing wrong ?
EDIT
I found some part of my problem: I moved my style from the "Resource" section to the "ItemContainerStyle" section as defined below in code sample. This way the style is still applied and I can access the style from the TReeView.ItemContainerStyle property in code. But I still don't know how to get the TreeViewItem selected style like the color of the background by code ??
I have xaml:
<TreeView Name="TreeViewSelectScopeStudy" MinHeight="24" Margin="7"
ItemsSource="{Binding Path=TvItemRootPssTreeViewRoot.ChildsView}" Height="Auto"
VerticalAlignment="Stretch"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsHilighted}" Value="true">
<!--<Setter Property="Background" Value="SlateBlue"></Setter>-->
<Setter Property="Background" Value="{StaticResource {x:Static SystemColors.HighlightBrushKey}}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
I have that code:
public static void EnableMultipleSelection(TreeView treeView)
{
if (!_isMultiSelectTreeViewLeftButtonHandlerRegistered)
{
EventManager.RegisterClassHandler(typeof(TreeViewItem), UIElement.MouseDownEvent,
new MouseButtonEventHandler(TreeViewMouseDownGlobal));
_isMultiSelectTreeViewLeftButtonHandlerRegistered = true;
}
DataTrigger dataTrigger = new DataTrigger();
dataTrigger.Binding = new Binding("IsHilighted");
dataTrigger.Value = true;
dataTrigger.Setters.Add(new Setter(TreeViewItem.BackgroundProperty, new SolidColorBrush(Colors.Brown)));
treeView.ItemContainerStyle.Triggers.Add(dataTrigger);
Edited code:
<TreeView Name="TreeViewSelectScopeStudy" MinHeight="24" Margin="7"
ItemsSource="{Binding Path=TvItemRootPssTreeViewRoot.ChildsView}" Height="Auto"
VerticalAlignment="Stretch"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsHilighted}" Value="true">
<Setter Property="Background" Value="{StaticResource {x:Static SystemColors.HighlightBrushKey}}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<!--<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsHilighted}" Value="true">
<Setter Property="Background" Value="{StaticResource {x:Static SystemColors.HighlightBrushKey}}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>-->
It sounds like you are trying to set the Style.Trigger too soon, before the template has been applied. If you apply the Trigger after the FrameWorkElement you are targeting has fully loaded you'll probably find that the ItemStyleContainer is no longer null.
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.loaded.aspx
Implementing OnApplyTemplate for your FrameworkElement should hook you in after the ItemStyleContainer has been set as it is called after the Visual Tree has been rendered.
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.onapplytemplate.aspx
This seems like a pretty common scenario but I can't figure out how to bind the menu items to disable when there is nothing in the clipboard.
I've decided against using the Windows clipboard and instead store the actual object in a reference variable on the UserControl called NodeClipboard. Since it is strongly typed and implements INotifyProperty it is a lot more useful to me than the Windows clipboard.
Binding to the individual item works fine though it is extremely verbose because you can't set EventHandlers within resources without using the Style Event Setters.
It sort of looks like this...
<UserControl x:Name="PART_Root">
<TreeView x:Name="PART_Tree" ItemsSource="{Binding ElementName=PART_Root, Path=RootItemContainer}">
<TreeView.Resources>
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Style>
<Style TargetType="ContextMenu">
<!-- I use this event to select the tree view item otherwise it is actually pretty difficult to know what item you right clicked on -->
<EventSetter Event="Opened" Handler="ContextMenu_Opened"/>
</Style>
</ContextMenu.Style>
<MenuItem Header="Cut">
<MenuItem.Style>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="CutNode_Click"/>
<Style.Triggers>
<!-- This binding is fine because it binds to the item that was right clicked on -->
<DataTrigger Binding="{Binding Path=IsRoot}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<MenuItem Header="Paste">
<MenuItem.Style>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="PasteNode_Click"/>
<!-- This binding always fails because ContextMenu lives outside of the logical tree -->
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</ContextMenu>
<Style TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Id}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</UserControl>
The key part that doesn't work is this here:
<!-- This binding always fails because ContextMenu lives outside of the logical tree -->
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
I've tried using relative source which results in the same problem. The only solution I've thought of so far is making two context menus, one with Paste enabled and one without, and switching the context menu on the style on the TreeViewItem style's ContextMenu setter like so...
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu_PasteEnabled}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PART_Root, Path=NodeClipboard" Value="{x:Null}">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu_PasteDisabled}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.Resources>
Use the Clipboard class. Then you can use the ContainsText method to determine if there is any data on the clipboard.
http://msdn.microsoft.com/en-us/library/system.windows.forms.clipboard.aspx
This is a question following my previous problem, you can find it right there
So. Now I defined a DataGrid with a specific ElementStyle for each column (which just defines the TextBlocks inside in bold & white -- will come over this problem later)
So now I have two questions
First question (solved)
When I happen to set a background to my cell, it overrides the default style, and the background stays the same when the cell is highlighted.
One example of a style:
<!-- Green template for market-related -->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
I'd naturally say that this is "normal" because I set the Grid's background to Green. I therefore tried it this way:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="LightGreen">
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="True">
<Setter Property="Grid.Background" Value="#FF3774FF" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
This won't work either. As you can see I put a DebugConverter so I can check that the trigger is actually called, which is the case, but... Background does not change (and Snoop confirms this...)
Third try:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<ControlTemplate.Resources>
<Style TargetType="{x:Type tk:DataGridCell}">
<Setter Property="Background" Value="LightGreen" />
</Style>
</ControlTemplate.Resources>
<Grid>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
And... No background will be displayed (stays transparent)
So I think I am working in the wrong way here and I was wondering what should I do to JUST define the "not selected" template.
I would say that I may need to define a style BasedOn the "classic" style but, how would I do that? I tried to add TemplateBindings with no success
** EDIT: Solution**
As H B suggested in his answer, problem was coming from DependencyProperty Precedence, here's the solution:
<!-- Light green template for sophis-related -->
<ControlTemplate x:Key="LightGreen" TargetType="{x:Type tk:DataGridCell}">
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="True">
<Setter Property="Grid.Background" Value="#FF316AC5" />
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}" Value="False">
<Setter Property="Grid.Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
Second question
Now, let's speak Triggers.
Basically, what I want to do is to define specific Triggers to my ElementStyle so the font color is white if the cell's background is Red or Green (the only aim of this is to have a better readability as Red and Green are kinda dark, black font on dark background results in a nice fail :p )
Edit Seems like I'm not clear enough: the following style is the style applied to each item of the datagrid, through the property DataGridTextColumn.ElementStyle. Here is the code handling that:
void VolatilityDataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataGridTextColumn column = e.Column as DataGridTextColumn;
column.ElementStyle = s_boldCellStyle;
// Other stuff here...
}
Here is what I do:
<!-- Cell style for colored matrix-->
<Style x:Key="BoldCellStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}}}"
Value="Red">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tk:DataGridCell}},
Converter={StaticResource DebugConverter}}"
Value="Green">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
And... It doesn't work. Strangely, what goes through converter is ONLY transparent background colors. I am definitely missing something here!
BTW, I also tried with classic triggers, no success either, I use DataTriggers here so I can debug the binding values!
Now I've been stuck for more than three days on this and I'm starting to freak out... Hopefully the Stackoverflow community will save me :)
Thanks!
Edit
Okay, update.
I understood why my Trigger does not work. The Background actually set is on the Grid and NOT on the DataGridCell. It is therefore normal that I don't get any color set there.
However, I ran some tests and found out that when the binding is set, the TextBlock does not have any parent yet (Parent = null). Binding to a RelativeSource of type Grid will bind me to... The whole DataGrid items presenter.
I'm not sure what to do now, since it seems like that from the actual TextBlock style I can't reach the parent Grid and therefore cannot resolve what color should I display according to the background.
Also, I can't change the Font color in my ControlTemplate because the DataGrid wants a Style for each column, which overrides the template's style by default (see my previous question and its answer)
So... Stuck again I am!
Dependency Property Value Precedence
This:
<Grid Background="LightGreen">
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<!-- Trigger Stuff -->
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
Needs to be:
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="LightGreen"/>
<!-- Trigger Stuff -->
</Style>
</Grid.Resources>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
Not sure about your second question as of now, possibly a related problem, i would suggest setting TextElement.Foreground instead of Foreground for starters. Getting Transparent as value is not very helpful, what control template do you use for the DataGridCell? If it is custom, is the Background hooked up properly via a TemplateBinding?
This works as long as the Background property is used, so if you have a ControlTemplate which sets things internally you need to externalize that. A normal DataGrid example:
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="LightGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Content}" Value="Apple">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content}" Value="Tomato">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Value="Red">
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
<DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type DataGridCell}}}" Value="Green">
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
So if the CellStyle sets the ControlTemplate the properties need to be hooked up via TemplateBinding. e.g.
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="LightGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Content}" Value="Apple">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content}" Value="Tomato">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
Do not do the triggering inside the template or it will get messy.
I have two resources Dock and Undock in my View which is a UserControl(Dock.xaml), Following is xaml code
<Grid>
<ContentControl Template="{StaticResource Dock}"/>
</Grid>
In DockViewModel there is property called IsDocked,if its true i need to apply Dock otherwise Undock template
How to change the template in view using ViewModel.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentControl.Template" Value="{StaticResource Dock}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsDocked}" Value="False">
<Setter Property="ContentControl.Template" Value="{StaticResource UnDock}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>