WPF Custom Control That Displays Content Inline or in a Popup - c#

I am creating a custom WPF content control that has a DisplayMode property which can be:
Inline
Popup
When DisplayMode="Inline", my ControlTemplate can use a standard ContentPresenter like normal.
However, when DisplayMode="Popup", I want the Content to be displayed in a Popup control.
How should I solve this problem?
Does it have to happen purely in code when the DisplayMode property changes? How do I move the content of the Content property between a ContentPresenter and the Popup?

It looks like I was trying to make this more complex than it really is.
The solution to this was to create two separate ControlTemplate(s). One that displays inline and one that displays in a Popup control.
Next, all I had to do was create a couple style triggers that change the ControlTemplate based on the value of the DisplayMode property.
It looks like this:
<Style x:Key="MyControlStyle" TargetType="{x:Type my:MyControl}">
<Setter Property="Template" Value="{StaticResource InlineTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DisplayMode}" Value="Inline">
<Setter Property="Template" Value="{StaticResource InlineTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding DisplayMode}" Value="Overlay">
<Setter Property="Template" Value="{StaticResource OverlayTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>

Related

WPF DataTrigger Based on Color

I'm trying to change the background of control A based on the background of control B, using a DataTrigger.
I've attempted to do this the conventional way you would change any property in a DataTrigger. Worst case I figure I can just use a converter, but wanted to try this first. Where have I gone astray, or is there simply no way to do this (i.e., bind to a control's background in a DataTrigger)?
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding Background, ElementName=Icon}" Value="Black">
<Setter Property="Background" Value="LimeGreen"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>

Deriving (Expanding) a Control Template in WPF

I have this DataGrid I want to change the Disabled behaviour (xaml);
I want to change this small part in the template.
If it is not possible i dont mind to use:
<Setter Property="OverridesDefaultStyle" Value="True"/>
And To replace the entire xaml (template) of my control, But I need the complete template to copy paste and help where to change the Disabled look-like part.
Can anyone help me?
EDIT: It's been pointed out that all the default control templates are available at MSDN which makes the below relevant, but I'll leave it here for interest.
Given an instance of a control you get serialize the markup for the control template using the System.Windows.Markup.XamlWriter class.
To get a control template:
string markup = System.Windows.Markup.XamlWriter.Save(control.Template);
To get a complete dump (including triggers etc) of the control template use.
StringBuilder markupBuilder = new StringBuilder();
XmlWriter writer = XmlWriter.Create(markupBuilder);
System.Windows.Markup.XamlDesignerSerializationManager manager =
new System.Windows.Markup.XamlDesignerSerializationManager(writer);
manager.XamlWriterMode = System.Windows.Markup.XamlWriterMode.Value;
// data grid named dataGrid1
var template = dataGrid1.Template;
System.Windows.Markup.XamlWriter.Save(dataGrid1.Template, manager);
string markup = markupBuilder.ToString();
If you just looking to change the foreground color of the DataGrid when it's disabled, you should be able to use styles together with triggers rather than replacing the entire template.
<DataGrid>
<DataGrid.Resources>
<Style
TargetType="{x:Type DataGridColumnHeader}">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Value="False">
<Setter
Property="Foreground"
Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style
TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Value="False">
<Setter
Property="Foreground"
Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<!-- Column Definitions -->
</DataGrid>
Adding the above 2 styles to the DataGrid's resources collection will, set the foreground of each column header and data row cell to green when the DataGrid is disabled.
To define the disabled behavior of any control, you should change the Disabled visual state accordingly in the control template.

Is it possible to reload XAML properties of a UserControl?

I have some frameworkElements inside XAML, and I define some properties like background, and cursor.
In code behind, I change these properties, and when an event triggers, I want to reload these initial properties defined in XAML. Is this possible or I need to redifine manually in code behind?
Thanks.
A control defined in XAML is essentially defining an instance. Once you have the instance, the object is just like every other object you deal with. Having access to the instance defined in XAML within your code behind is akin to creating a new object in the code behind and then adjusting its properties at run time.
When you want the property value to change; you don't revert your property changes, you simply change them to what you desire.
I would suggest looking into DataTriggers for making temporary changes based on some value. This will change the value of a property while a specific condition is true, and revert it to its original value when the condition is false.
For example, here's a style that will change the cursor to a Wait cursor while loading, and change the background to Red if it is invalid.
<Style TargetType="{x:Type local:MyUserControl}">
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="Background" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoading}" Value="True">
<Setter Property="Cursor" Value="Wait" />
</DataTrigger>
<DataTrigger Binding="{Binding IsValid}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
Of course, you'll have to define the IsLoading and IsValid properties behind your UserControl, and set them to true/false at the appropriate times in your code-behind.

Trigger on ContextMenu.IsOpen in XAML

Here's what I'm trying to do:
<Style x:Key="TreeViewItemStyle">
<Setter Property="TreeViewItem.ContextMenu" Value="{StaticResource ContextMenu}" />
<Style.Triggers>
<Trigger Property="TreeViewItem.ContextMenu.IsOpen" Value="True">
<Setter Property="TreeViewItem.BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="Yellow" GlowSize="2"/>
</Setter.Value>
</Setter>
</Trigger>
</Style>
...
But it is obviously not working because Property="TreeViewItem.ContextMenu.IsOpen" is not recognized. Any suggestions to what I need to change?
You can bind to the IsOpened property of the context menu using a DataTrigger:
<DataTrigger Binding="{Binding ContextMenu.IsOpen, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
Unfortunately, since all of the items in TreeView share the same ContextMenu, that will highlight all of them at once. There doesn't seem to be a property that lets you find out which FrameworkElement opened the ContextMenu.
You could handle the ContextMenuOpening and ContextMenuClosing events on the TreeViewItem, since those will bubble up from the control that handled the click and pass through the right TreeViewItem. If you want to do it in XAML, you could use an EventTrigger to start a one-frame animation that changes your property. The cleanest option may be to write an attached behavior that handles the ContextMenuOpening and ContextMenuClosing events and sets an attached property to true when the context menu is open.

How do I know whether to use a style or override a control template?

This question is inspired by this recent question and other situations I've encountered in my WPF development. How do I know whether it is enough to set a style on a control to override some default behavior vs creating a new control template?
More concretely, in the question above, the author wants to change the look of a ListBoxItem when it is selected. (See code reprinted below). Everything works, except the Background property. How is one supposed to know that they should override the Control Template for this?
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Path=Name}"/>
<Setter Property="Margin" Value="2"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
As to whether to use a style or template Ray provided a great response.
As to how to solve your problem without creating a template, maybe I can help.
The background color is being set by the SystemColors. Using Blend and creating a template you can see the exact xaml.
So if NO TEMPLATES! is a requirement you can always change what that resource is.
Example :
<ListBox>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Yellow" />
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Path=Name}"/>
<Setter Property="Margin" Value="2"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBoxItem>Test 1</ListBoxItem>
<ListBoxItem>Test 2</ListBoxItem>
<ListBoxItem>Test 3</ListBoxItem>
</ListBox>
That will give you the background color for that given ListBox and not screw up anything else in the app.
Styles can be thought of very closely to CSS styles in HTML. If all you want to do is change the basic properties of a control such as Background, Foreground or whatever properties it exposes then a Style is exactly what you need. Styles also allow you to apply triggers so for animations, a style is also sufficient.
If you're finding you want to change the intrinsice behaviours / inner workings on a control then a control template is what you want. For example, if you want to change how a button is laid out by adding some sort of grid behaviour, then using a control template is the way forward.
Unfortunately, for your specific example, you don't know unless you try it. Basically you first try it with a Style....and if that doesn't work for whatever reason, then you write a ControlTemplate. You usually only end up writing ControlTemplates for the reasons Ray mentioned.
My guess is that the trigger you're trying to set has also been hardcoded in the ControlTemplate...which is bad design imo because it prevents the Style from overriding it.
By "Background" I take it to mean the "blue" rectangle that surrounds the ListBoxItem when it is selected?
This is actually the FocusVisualStyle property, which is a style that describes what the item should look like when it is focused. The Control explicitly sets this property (described here), so in order to override it, you will have to redefine the Control Template, making sure to use a default Style setter to set it to {x:Null}.

Categories