How to bind to another's object property with XAML Style Template? - c#

Let's say I have the following class:
public class MyClass : System.Windows.FrameworkElement
{
public static readonly DependencyProperty HasFocusProperty = DependencyProperty.RegisterAttached("HasFocus", typeof(bool), typeof(MyClass), new PropertyMetadata(default(bool)));
public bool HasFocus
{
get => (bool)GetValue(HasFocusProperty);
set => SetValue(HasFocusProperty, value);
}
public System.Windows.Controls.TextBox TextBox { get; set; }
}
I want to change some UI properties of TextBox via XAML Template Trigger based on the property HasFocus, so I do the following:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:win="clr-namespace:System.Windows.Controls">
<Style TargetType="{x:Type win:TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type win:TextBox}">
<ControlTemplate.Triggers>
<Trigger Property="MyClass.HasFocus" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Red" />
<Setter TargetName="Border" Property="BorderThickness" Value="2" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
However, the style is not applied when setting HasFocus = true.
Within the properties of TextBox, I can see that the trigger is registered. If I change <Trigger Property="MyClass.HasFocus" Value="True"> to <Trigger Property="MyClass.HasFocus" Value="False">, my style is applied initially. So I think my XAML definition is okay.
Any ideas how to solve this?

An element in a template that is applied to a TextBox cannot bind to a property of a MyClass, unless there is a MyClass element to bind to somewhere in the visual tree.
If you want to be able to set a custom HasFocus property of a TextBox, you should create an attached property:
public class FocusExtensions
{
public static readonly DependencyProperty SetHasFocusProperty = DependencyProperty.RegisterAttached(
"HasFocus",
typeof(bool),
typeof(FocusExtensions),
new FrameworkPropertyMetadata(false)
);
public static void SetHasFocus(TextBox element, bool value)
{
element.SetValue(SetHasFocusProperty, value);
}
public static bool GetHasFocus(TextBox element)
{
return (bool)element.GetValue(SetHasFocusProperty);
}
}
It can be set for any TextBox element:
<TextBox local:FocusExtensions.HasFocus="True">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="local:FocusExtensions.HasFocus" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Related

WPF, setting background color with trigger

I have a customcontrol which render a textbox. I've also a style that set the color of the background based on some conditions as follows:
<Style x:Key="ArtParamStyle" TargetType="av:DC_Base">
<Setter Property="Background" Value="{StaticResource EditableAreaBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Info.Upd.IsAutoCalc}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Forced}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
Initially as the value of my textbox is autocalculated, the background is correctly red. If I also set the Forced as true (by ticking a chebckbox) I've a weird result, the border of textbox is lightgreen but background not.
It seems to be a strange color, a combination of red and lightgreen. As test, if I set the "IsAutoCalc" color as Transparent, the trigger works correctly. How can I solve this?
your code seems to be correct. But I provide you my sample:
XAML:
<Window.Resources>
<Style x:Key="ArtParamStyle" TargetType="TextBox">
<Setter Property="Background" Value="Yellow" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Bool1}" Value="True"/>
<Condition Binding="{Binding Bool2}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Red" />
<Setter Property="BorderBrush" Value="Red" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding Bool2}" Value="True">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="BorderBrush" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Style="{StaticResource ArtParamStyle}" Height="50" Margin="4"/>
<CheckBox IsChecked="{Binding Bool1}"/>
<CheckBox IsChecked="{Binding Bool2}"/>
</StackPanel>
</Grid>
In this case I used Multidatatrigger to set red background when Bool2(your forced) is not checked.
MainWindow.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChaged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private bool bool1;
public bool Bool1
{
get { return bool1; }
set { bool1 = value; RaisePropertyChaged("Bool1"); }
}
private bool bool2;
public bool Bool2
{
get { return bool2; }
set { bool2 = value; RaisePropertyChaged("Bool2"); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
Probably your problem is related to your custom control.

Change windows style on click WPF

I want to change style on click, but simply cant find any way to do it.
Here is styles
<Style x:Key="CustomWindowStyle" TargetType="{x:Type Window}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="MinWidth" Value="200"/>
<Setter Property="MinHeight" Value="46"/>
<!--CaptionHeight + ResizeBorderThickness * 2-->
<Setter Property="Background" Value="Yellow"/>
<Setter Property="BorderBrush" Value="Green"/>
<Setter Property="BorderThickness" Value="7"/>
<Setter Property="Foreground" Value="DarkRed"/>
<Setter Property="Template" Value="{StaticResource WindowTemplate}"/>
</Style>
<!--the red style window-->
<Style x:Key="RedWindowStyle" TargetType="{x:Type Window}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="MinWidth" Value="100"/>
<Setter Property="MinHeight" Value="46"/>
<Setter Property="Background" Value="white"/>
<Setter Property="BorderBrush" Value="DarkRed"/>
<Setter Property="BorderThickness" Value="7"/>
<Setter Property="Foreground" Value="DarkGray"/>
<Setter Property="Template" Value="{StaticResource WindowTemplate}"/>
</Style>
I want to change it in MainWindow.xaml.cs
private void button_Click(object sender, RoutedEventArgs e)
{
this.Style ?? // dont know what to do
}
Please help
I would probably bind this in my style. For example:
MainWindow.xaml:
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="local:MainWindow">
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="True">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Click="Button_Click">Click Me, Yo!</Button>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp2
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _someProperty;
public bool SomeProperty
{
get { return _someProperty; }
set
{
_someProperty = value;
OnPropertyChanged();
}
}
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SomeProperty = !SomeProperty;
}
}
}
Essentially, you derive the state of your window from some property in your ViewModel, using a converter if needed.
Of course, if you want a direct solution, you could just use FindResource:
this.Style = this.FindResource("MyLocalStyleResourceName") as Style;
This is described here.

How can I reset the ThemeResource of DataTemplate?

I have defined a DataTemplate that includes a textbox. In "glove mode" I need a large font/minHeight so the touch screen works nicely, but in "office mode" I want different set of values. I believe this should be possible, but can't figure it out.
How can I modify the theme in the code behind? Or if this is totally wrong, how should I do it?
Styles:
<Style x:Key="GloveTextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="30"/>
<Setter Property="MinHeight" Value="60"/>
</Style>
<Style x:Key="OfficeTextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinHeight" Value="30"/>
</Style>
DataTemplate:
<DataTemplate x:Key="InspectionItemStringTemplate" x:DataType="data:InspectionItem"><TextBox Text="{x:Bind NewValue,Mode=TwoWay}"
x:Name="MyTextBox"
x:Phase="1" Style="{ThemeResource GloveTextBoxStyle}"/></DataTemplate>
What about IValueConverter?
You can create something like that:
public class TextBoxStyleConverter : IValueConverter
{
public Style GloveTextBoxStyle { get; set; }
public Style OfficeTextBoxStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, string language)
{    
// analyze binded value and return needed style
return condition ? GloveTextBoxStyle : OfficeTextBoxStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
and in your XAML code
<local:TextBoxStyleConverter x:Key="TextBoxStyleConverter">
<local:TextBoxStyleConverter.GloveTextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="FontSize" Value="30"/>
<Setter Property="MinHeight" Value="60"/>
</Style>
</local:TextBoxStyleConverter.GloveTextBoxStyle>
<local:TextBoxStyleConverter.OfficeTextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinHeight" Value="30"/>
</Style>
</local:TextBoxStyleConverter.OfficeTextBoxStyle>
</local:TextBoxStyleConverter>
<DataTemplate x:Key="InspectionItemStringTemplate"
x:DataType="data:InspectionItem">
<TextBox Text="{x:Bind NewValue,Mode=TwoWay}"
x:Name="MyTextBox"
x:Phase="1"
Style="{Binding Converter={StaticResource TextBoxStyleConverter}}"/>
</DataTemplate>

Style Trigger on sub-property of a DependencyProperty

I have a control I want to style depending on the set MessageType of a custom Message type DependencyProperty of that control.
Custom Type:
public class Message : ObservableObject
{
public MessageTypes MessageType
{
get { return _messageType; }
set {
RaisePropertyChanged(() => MessageType);
_messageType = value;
}
}
public string Text { ... }
...
}
Control:
public class MessageControl : Control
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register(
"Message",
typeof(Message),
typeof(MessageControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public Message Message
{
get
{
return (Message)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
}
}
}
Style:
<ControlTemplate x:Key="MessageControlTemplate"
TargetType="controls:MessageControl">
<Border Background="{TemplateBinding Background}">
<TextBlock Text="{Binding Path=Message.Text,
RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
</ControlTemplate>
<Style TargetType="controls:MessageControl">
<Setter Property="Template"
Value="{StaticResource MessageControlTemplate}" />
<Style.Triggers>
<!-- HERE IS THE ISSUE -->
<Trigger Property="Message.MessageType"
Value="{x:Static classes:MessageType.Error}">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
So the problem is that I can't add a Trigger for Message.MessageType (or basically any other sub property).
Is it even possible to accomplish this? Or should I just create two properties in MessageControl for Text and MessageType?
You could do it with a DataTrigger:
<Style TargetType="controls:MessageControl">
...
<Style.Triggers>
<DataTrigger Binding="{Binding Message.MessageType,
RelativeSource={RelativeSource Self}}"
Value="{x:Static classes:MessageType.Error}">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
You should also raise the PropertyChanged event after setting the property's backing field:
public MessageType MessageType
{
get { return _messageType; }
set
{
_messageType = value;
RaisePropertyChanged(() => MessageType);
}
}

DataTrigger triggered by boolean

I want to change the foreground setter property using a boolean equals true.
I tried:
public bool RED = false;
if (condition)
{
RED = true;
}
and have the DataTrigger triggered by the boolean:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="16" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RED}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
But no luck. Whats the best/simplest way to write this?
You need to create a property. What you have is a field. Also, you need to implement INotifyPropertyChanged or use a DependencyProperty or other type type of property that is able to notify a WPF binding.
private bool _red;
public bool Red {
get { return _red; }
set
{
_red = value;
OnPropertychanged();
}
}

Categories