I have a UserControl:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Button Name="myButton" Content="hello world" Padding="10" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</UserControl>
and code-behind:
public partial class UserControl1 : UserControl
{
public bool IsShown
{
get { return (bool)GetValue(IsShownProperty); }
set { SetValue(IsShownProperty, value); }
}
public static readonly DependencyProperty IsShownProperty =
DependencyProperty.Register("IsShown", typeof(bool), typeof(UserControl1), new UIPropertyMetadata(false));
public UserControl1()
{
InitializeComponent();
}
}
I want to add an Effect to myButton when IsShown is true. In the MainWindow,
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
<Window.Resources>
<DropShadowEffect x:Key="outerGlow" Color="#00E300" Direction="0" ShadowDepth="0" BlurRadius="12" />
<Style x:Key="style" TargetType="my:UserControl1">
<Style.Triggers>
<Trigger Property="IsShown" Value="true">
<Setter Property="myButton.Effect" Value="{StaticResource outerGlow}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<my:UserControl1 x:Name="userControl11" Style="{StaticResource style}" />
</Window>
But VS cannot solve symbol myButton.
How to fix it?
Update:
I found the MSDN article. Can anyone tell me which category my property path falls in?
You can't do that.
If you need to affect a control which is inside the scope of a UserControl, you will need to define a property IN the UserControl and reference that in your inner control using RelativeSource or ElementName:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="mycontrol">
<Button Name="myButton" Content="hello world" Padding="10" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsShown, ElementName="mycontrol"}" Value="True">
<Setter Property="Effect" Value"{StaticResource OrWhatever}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl>
When you need to set this effect in a Style that can be applied externally, you could create another property MyButtonEffect and set that in the Trigger.
public partial class UserControl1 : UserControl
{
public Effect MyButtonEffect
{
get { return (Effect)GetValue(MyButtonEffectProperty); }
set { SetValue(MyButtonEffectProperty, value); }
}
public static readonly DependencyProperty MyButtonEffectProperty =
DependencyProperty.Register("MyButtonEffect", typeof(Effect), typeof(UserControl1),
new FrameworkPropertyMetadata(null, MyButtonEffectPropertyChanged));
private static void MyButtonEffectPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((UserControl1)obj).myButton.Effect = (Effect)e.NewValue;
}
}
The Trigger:
<Trigger Property="IsShown" Value="true">
<Setter Property="MyButtonEffect" Value="{StaticResource outerGlow}"/>
</Trigger>
Related
I'm currently working on an application with AvaloniaUI and C#.net. My application has a MainWindow that uses one ViewModel(called MainWindowViewModel) and the Window also contains two UserControls that are integrated via TabControl.
So the issue that I now have is, that I want give each UserControl its own ViewModel, so far I have created a ViewModel for one of my UserControls and also set the namespace to it in the axaml-File of my Control. I've set the DataContext also, but the ViewModel is never been loaded.
As follows here's the source code of my MainWindow, the UserControl und the ViewModel of my UserControl:
MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views ="clr-namespace:MyApp.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyApp.Views.MainWindow"
WindowStartupLocation="CenterScreen"
Icon="/Assets/Programmicon.png"
Title="{Binding WindowTitle}" CanResize="False" >
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Window.Styles>
<Style Selector="TabItem">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Height" Value="34"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Background" Value="#148198"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0 0 0 0"/>
<Setter Property="Padding" Value="10 0"/>
</Style>
<Style Selector="TabControl WrapPanel">
<Setter Property="Background" Value="#148198"/>
</Style>
<Style Selector="TabItem:selected">
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Margin" Value="0 0 0 0"/>
<Setter Property="Padding" Value="10 0"/>
</Style>
</Window.Styles>
<Grid>
<TabControl Name="tabMenu" Background="White">
<TabItem Header="Import" VerticalContentAlignment="Center" >
<views:ImportView/>
</TabItem>
<TabItem Header="Einstellungen" VerticalContentAlignment="Center">
<views:SettingsView/>
</TabItem>
</TabControl>
<Label Name="lblErrorInfo" Content="" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" ZIndex="10" Background="Red" Foreground="White" FontSize="34" FontWeight="Bold" IsVisible="false"></Label>
</Grid>
</Window>
SettingsView.axaml
<UserControl xmlns="https://github.com/avaloniaui"
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:vm="using:MyApp.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyApp.Views.SettingsView">
<this.DataContext>
<vm:SettingsViewModel/>
</this.DataContext>
<DataGrid Name="gEventlog" Items="{Binding EventlogData}" AutoGenerateColumns="False" CanUserResizeColumns="False" CanUserReorderColumns="False" Background="LightGray" CanUserSortColumns="True" Canvas.Top="20" Width="1024" Height="626" GridLinesVisibility="All" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" AlternatingRowBackground="Azure">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"/>
<DataGridTextColumn Header="Zeitstempel"/>
<DataGridTextColumn Header="Event-Typ"/>
<DataGridTextColumn Header="Benutzer"/>
<DataGridTextColumn Width="563" Header="Fehlermeldung"/>
<DataGridTextColumn Header="Funktion"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>
SettingsViewModel.cs
using MyApp.Classes;
using Microsoft.Data.Sqlite;
using System;
using System.Data;
namespace MyApp.ViewModels
{
public class SettingsViewModel : ViewModelBase
{
// The appconfig class
private readonly AppConfiguration _appConfig;
// The utils class
private readonly Utils _utils;
// The eventlog class
private readonly Eventlog _event;
private string _importFilesPath;
// The data of the eventlog-grid
private DataView _eventlogData;
public DataView EventlogData
{
get { return _eventlogData; }
set
{
if (_eventlogData == value)
{
return;
}
_eventlogData = value;
}
}
public string ImportFilesPath
{
get { return _importFilesPath; }
set
{
if (_importFilesPath == value)
{
return;
}
_importFilesPath = value;
}
}
public SettingsViewModel()
{
// Initialize the members
_appConfig = new AppConfiguration();
_utils = new Utils();
_event = new Eventlog();
_eventlogData = new DataView();
_importFilesPath = "";
this.InitializeGUI();
}
private void InitializeGUI()
{
//Fill the eventlog grid
LoadEventlog();
_importFilesPath = _appConfig.ImportPath;
}
}
}
So my question is, how can I connect my UserControl with the corresponding ViewModel?
I'm new to AvaloniaUI, but coming from WPF where that approach (Give every UserControl an own ViewModel) works. Maybe I am overseeing some essential things.
Thanks in advance for every answer
If you have created your application using MVVM Avalonia template you should have a file called ViewLocator which already "connects" the view model with the corresponding view. Then if you want to display your settings in the main window you can add a property to the MainWindowViewModel:
public SettingsViewModel Settings { get; }
And bind it in the MainWindow
<ContentControl Content="{Binding Settings}"/>
I'm using reflection and expression trees in C# to build a fairly adaptable search tool for our database. Because of this, I have a need for a custom ContentControl - termed 'MultiStyleInputBox' - which uses data triggers to adjust its ContentTemplate to the Type of input expected. The problem is, while the code builds just fine and I have confirmed that both the public and static constructors are being hit when the code executes, the ContentControl's content doesn't show up at all in my UI.
Now, I'm relatively new to writing custom XAML/C# UI control classes, but I have been able to cobble together the following:
<ContentControl x:Class="MyApp.MultiStyleInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Resources>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Resources>
</ContentControl>
And the code behind:
public sealed partial class MultiStyleInputBox : ContentControl
{
//Dependency properties
public Type InputType
{
get { return (Type)GetValue(InputTypeProperty); }
set { SetValue(InputTypeProperty, value); }
}
public static readonly DependencyProperty InputTypeProperty =
DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox));
//Constructors
public MultiStyleInputBox() : base()
{
}
static MultiStyleInputBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
}
}
I thought at one point that I might not have set the content of the ContentControl, and so I added a <ContentPresenter/>, but I received an error saying that the content is set more than once, so I believe that my <Style.Setters></Style.Setters> section is taking care of that. Otherwise, even running around using PresentationTraceSources.TraceLevel="High" on my bindings, I haven't so far been able to run into any useful errors.
Is there some sort of glaring issue in my code that I can immediately address (hopefully)? Do I need to reevaluate my approach to the problem?
Update
After suggested corrections in the answers below, here is the latest version of the code:
<ContentControl x:Class="MyApp.MultiStyleInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox, Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Style>
</ContentControl>
And the code-behind:
public partial class MultiStyleInputBox : ContentControl
{
//Dependency properties
public Type InputType
{
get { return (Type)GetValue(InputTypeProperty); }
set { SetValue(InputTypeProperty, value); }
}
public static readonly DependencyProperty InputTypeProperty =
DependencyProperty.Register("InputType", typeof(Type), typeof(MultiStyleInputBox));
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(DateTime.Now,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
//Constructors
public MultiStyleInputBox() : base()
{
}
static MultiStyleInputBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(typeof(MultiStyleInputBox)));
}
}
Here is a test instantiation of the MultiStyleInputBox (I'm using Mahapps.Metro):
<Controls:MetroWindow
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
x:Class="MyApp.TestWindow"
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:MyApp"
Title="Test Window" Height="450" Width="800"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<StackPanel>
<local:MultiStyleInputBox x:Name="TestMultiBox" Value="1" InputType="{x:Type sys:Int32}"/>
</StackPanel>
</Controls:MetroWindow>
When I try to instantiate this class, I'm still not getting anything showing up in my UI, and the ContentControl isn't taking up any space. Even if I include Width="50" Height="24", I still get nothing. I've tested setting both Value and InputType in code-behind and using a breakpoint to inspect the object, and I'm finding that, while both values get set, the Content of the ContentControl remains null.
The immediate problem is that your style isn't applied to the ContentControl you want to apply it to. You're defining an implicit ContentControl style which will be applied to any ContentControls you create in the content of this control -- but you aren't creating any, and that's not what you want anyhow.
For a quick fix, just change ContentControl.Resources to ContentControl.Style.
<ContentControl x:Class="MyApp.MultiStyleInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Name="multiStyleInputBox">
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource {x:Type ContentControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType, ElementName=multiStyleInputBox}" Value="{x:Type sys:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!--I have several of these triggers for different data types-->
</Style.Triggers>
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Value, ElementName=multiStyleInputBox}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ContentControl.Style>
</ContentControl>
Your next problem will be that selecting a new DateTime in the DatePicker won't update a property bound to the Value property of your viewmodel. Here's the fix for that:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(MultiStyleInputBox),
new FrameworkPropertyMetadata(DateTime.Now,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
And the last problem (or the first, perhaps) was that you weren't calling InitializeComponent() in the constructor, which is always required in any WPF codebehind class:
public MultiStyleInputBox()
{
InitializeComponent();
}
I am trying to create a few WPF UserControls to include in a library to share with my team but there is something wrong with how Read-Only properties are working for me.
For this question I made a very simple user control with two DependencyProperties. One that is based on an enum and the other that performs an action based on the selected enum. The enum is being used to choose a style the button will use.
The application is a regular Wpf Application with a Wpf User Control Library as a reference. I have a suspicion that the Control Library might be contributing to the problem so I felt it was relevant to the example.
Wpf Control Library1
Dictionary1.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfControlLibrary1">
<Style x:Key="SampleStyle-Font1">
<Setter Property="TextElement.FontFamily" Value="Wingdings" />
<Setter Property="TextElement.FontSize" Value="30" />
</Style>
<Style x:Key="SampleStyle-Font2">
<Setter Property="TextElement.FontFamily" Value="Elephant" />
<Setter Property="TextElement.FontSize" Value="30" />
</Style>
<Style x:Key="SampleStyle-Font3">
<Setter Property="TextElement.FontFamily" Value="Times New Roman" />
<Setter Property="TextElement.FontSize" Value="30" />
</Style>
</ResourceDictionary>
UserControl1.xaml:
<UserControl x:Class="WpfControlLibrary1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfControlLibrary1"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="200">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Template>
<ControlTemplate>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=Command}">
<StackPanel>
<Label Style="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=ReadOnlyStyle}" Content="{Binding Path=Content, RelativeSource={x:Static RelativeSource.TemplatedParent}}"></Label>
</StackPanel>
</Button>
</ControlTemplate>
</UserControl.Template>
</UserControl>
UserControl1.xaml.cs
namespace WpfControlLibrary1 {
using System.Windows;
using System.Windows.Controls;
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl {
public enum StyleSelector {
Style1,
Style2,
Style3
}
public static DependencyProperty SelectedStyleProperty =
DependencyProperty.Register("SelectedStyle", typeof(StyleSelector), typeof(UserControl1), new PropertyMetadata(ReadOnlyStyle_Changed));
private static readonly DependencyPropertyKey ReadOnlyStylePropertyKey =
DependencyProperty.RegisterReadOnly("ReadOnlyStyle", typeof(Style),
typeof(UserControl1), null);
public UserControl1() {
InitializeComponent();
}
public StyleSelector SelectedStyle {
get => (StyleSelector)GetValue(SelectedStyleProperty);
set => SetValue(SelectedStyleProperty, value);
}
public Style ReadOnlyStyle => (Style)GetValue(ReadOnlyStylePropertyKey.DependencyProperty);
private static void ReadOnlyStyle_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (!(d is UserControl1 userControl1)) {
return;
}
Style style;
switch (userControl1.SelectedStyle) {
case StyleSelector.Style1:
style = (Style)userControl1.FindResource("SampleStyle-Font1");
break;
case StyleSelector.Style2:
style = (Style)userControl1.FindResource("SampleStyle-Font2");
break;
case StyleSelector.Style3:
style = (Style)userControl1.FindResource("SampleStyle-Font3");
break;
default:
style = (Style)userControl1.FindResource("SampleStyle-Font1");
break;
}
userControl1.SetValue(ReadOnlyStylePropertyKey, style);
}
}
}
Wpf Application
MainWindow.xaml:
<Window x:Class="ReadOnlyDependencyPropertiesWithUserControls.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:ReadOnlyDependencyPropertiesWithUserControls"
xmlns:wpfControlLibrary1="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
mc:Ignorable="d"
Title="Example" Height="200" Width="400">
<StackPanel>
<wpfControlLibrary1:UserControl1 SelectedStyle="Style1" Content="This is the first control"></wpfControlLibrary1:UserControl1>
<wpfControlLibrary1:UserControl1 SelectedStyle="Style2" Content="This is the second control"></wpfControlLibrary1:UserControl1>
<wpfControlLibrary1:UserControl1 SelectedStyle="Style3" Content="This is the third control"></wpfControlLibrary1:UserControl1>
</StackPanel>
</Window>
The output below shows that the first control does not show a Style. If I run the application, switch it to Style2 using the Live editor and then back to Style1, the WingDings font does take over, but on a fresh run it does not. It definitely seems like a Dependency Property issue, but as far as I can tell I have the setup correct, especially since the other two controls seem to work.
Diagnosis
The reason why it does not work is because you assign the ReadOnlyStyle property value inside of SelectedStyle property changed handler. Since SelectedStyle is of type StyleSelector which is an enum, and you don't explicitly assign default value for this property, it will have default value of default(StyleSelector) assigned by the framework, which happens to be StyleSelector.Style1. And even if you explicitly assign that value to this property on your first control, the value doesn't really change, ergo the handler is not invoked, ergo ReadOnlyStyle remains null, ergo you get what you get (a Label with default style).
Solution
In order to remedy that, you should assign initial value of ReadOnlyStyle. But since the styles are kept in a resource dictionary, you cannot do that in the constructor. A good point to assign the initial value would be this:
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
var style = (Style)userControl1.FindResource("SampleStyle-Font1");
SetValue(ReadOnlyStylePropertyKey, style);
}
Better solution
"The WPF way" of achieving your goal would be to use triggers. So first of all you could remove unnecessary code from your control:
public partial class UserControl1 : UserControl
{
public enum StyleSelector
{
Style1,
Style2,
Style3
}
public static DependencyProperty SelectedStyleProperty =
DependencyProperty.Register("SelectedStyle", typeof(StyleSelector), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
public StyleSelector SelectedStyle
{
get => (StyleSelector)GetValue(SelectedStyleProperty);
set => SetValue(SelectedStyleProperty, value);
}
}
Then modify your temlpate:
<ControlTemplate>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:UserControl1}}, Path=Command}">
<StackPanel>
<Label x:Name="PART_Label" Content="{Binding Path=Content, RelativeSource={x:Static RelativeSource.TemplatedParent}}" />
</StackPanel>
</Button>
<ControlTemplate.Triggers>
<Trigger Property="local:UserControl1.SelectedStyle" Value="Style1">
<Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font1}" />
</Trigger>
<Trigger Property="local:UserControl1.SelectedStyle" Value="Style2">
<Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font2}" />
</Trigger>
<Trigger Property="local:UserControl1.SelectedStyle" Value="Style3">
<Setter TargetName="PART_Label" Property="Style" Value="{StaticResource SampleStyle-Font3}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Two important things here are:
The Label needs to have x:Name so it can be referenced in Setter.TargetName
Trigger.Property value needs to be fully qualified, because ControlTemplate does not have TargetType set.
I have problem related to WPF styles.
Let's use class (or prepare such) to contain DependencyProperty.
public class MyProperty : DependencyObject
{
public static readonly DependencyProperty exampleValueProperty = DependencyProperty.Register("exampleValue", typeof(bool), typeof(MyProperty));
public bool exampleValue
{
get { return (bool)this.GetValue(exampleValueProperty); }
set { this.SetValue(exampleValueProperty, value); }
}
}
public class MyTextBlock : TextBlock
{
public static readonly DependencyProperty myPropertyProperty= DependencyProperty.Register(
"myProperty", typeof(MyProperty), typeof(MyTextBlock));
public MyProperty myProperty
{
get
{
return (MyProperty)this.GetValue(myPropertyProperty);
}
set
{
this.SetValue(myPropertyProperty, value);
}
}
}
Now define style in xaml file and put 2 objects of class MyTextBlock on my main window grid:
<Window x:Class="WpfApplication1.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:WpfApplication1"
xmlns:custom="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type custom:MyTextBlock}">
<Setter Property="Background" Value="Aqua" />
<Setter Property="myProperty">
<Setter.Value>
<custom:MyProperty exampleValue="true" />
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid RenderTransformOrigin="0.514,0.47" Margin="100,0,0,0">
<custom:MyTextBlock x:Name="textBlock1" Height="80" Width="80" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,104,62.8,0" />
<custom:MyTextBlock x:Name="textBlock2" Height="80" Width="80" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,200,62.8,0" />
</Grid>
</Window>
And now the problem, when I change exampleValue for false using:
textBlock1.myProperty.exampleValue = false;
exampleValue is changed also for textBlock2.
As I can see, both textBlock1.myProperty and textBlock2.myProperty return same hashCode.
Probably this is because we firstly create one object of myProperty and then Setter just assign it (copy) to each MyTextBlock object.
Is there any way to use clone here? So every object will have his own "myProperty"?
I know that this one will works correctly, if I define my properties per object (but it looks like workaround, not solution):
<custom:MyTextBlock x:Name="textBlock1" Height="80" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,104,62.8,0">
<custom:MyTextBlock.myProperty>
<custom:MyProperty exampleValue="False"/>
</custom:MyTextBlock.myProperty>
</custom:MyTextBlock>
<custom:MyTextBlock x:Name="textBlock2" Height="80" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,200,62.8,0">
<custom:MyTextBlock.myProperty>
<custom:MyProperty exampleValue="False"/>
</custom:MyTextBlock.myProperty>
</custom:MyTextBlock>
It's the same instance of "MyProperty" because you create it as a resource.
And resources are shared / static by default.
Maybe setting "x:Shared" to false, might help:
<ResourceDictionary>
<Style TargetType="{x:Type custom:MyTextBlock}" x:Shared="false">
<Setter Property="Background" Value="Aqua" />
<Setter Property="myProperty">
<Setter.Value>
<custom:MyProperty exampleValue="true" />
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Anyhow I am not sure if it will do the trick, because it's the default, keyless style of your MyTextBlock control and it might be cached anyhow.
If it works than you have one instance of MyProperty for each MyTextBlock control but still with the same value for "exampleValue".
EDIT: Sorry didn't see #Brannon s comment ;)
I'm trying to change an image in a user control when a DependancyProperty on that user control is changed. As an example, I have the DependancyProperty StatusIndicator as a boolean. When it is true, I want to show the StatusOK image, and when its false I want to show the StatusBad image.
The data trigger seems to set the style of the image fine when the application first loads, since the StatusIndicator is false it sets the source of the image to StatusBad from StatusDisabled.
The problem is when I change the StatusIndicator value, the DataTriggers don't seem to notice. I monitored the change using WpfInspector, and it would always think that the StatusIndicator is false.
Below is the XAML.
<UserControl x:Class="WpfApplication6.StatusControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="335" d:DesignWidth="300" x:Name="ThisUserControl">
<UserControl.Resources>
<BitmapImage x:Key="StatusDisabled" UriSource="/WpfApplication6;component/Images/status_light_gray.png" />
<BitmapImage x:Key="StatusBad" UriSource="/WpfApplication6;component/Images/status_red.png" />
<BitmapImage x:Key="StatusOK" UriSource="/WpfApplication6;component/Images/status_light_green.png" />
<Style TargetType="{x:Type Image}" x:Key="StatusImage">
<Setter Property="Source" Value="{StaticResource StatusDisabled}" />
<Setter Property="RenderOptions.BitmapScalingMode" Value="HighQuality" />
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=ThisUserControl, Path=StatusIndicator}" Value="False">
<Setter Property="Source" Value="{StaticResource StatusBad}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ThisUserControl, Path=StatusIndicator}" Value="True">
<Setter Property="Source" Value="{StaticResource StatusOK}" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Style="{StaticResource StatusImage}" Grid.Row="0" />
</Grid>
</UserControl>
And the code-behind.
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication6
{
/// <summary>
/// Interaction logic for StatusControl.xaml
/// </summary>
public partial class StatusControl : UserControl
{
public StatusControl()
{
InitializeComponent();
}
public static readonly DependencyProperty StatusIndicatorProperty = DependencyProperty.RegisterAttached(
"StatusIndicator", typeof(bool), typeof(bool), new PropertyMetadata(false));
public bool StatusIndicator
{
get { return (bool)this.GetValue(StatusIndicatorProperty); }
set { this.SetValue(StatusIndicatorProperty, value); }
}
}
}
Any help would be greatly appreciated.
Thank you!
The second type should be your owner Type StatusControl.
public static readonly DependencyProperty StatusIndicatorProperty = DependencyProperty.RegisterAttached(
"StatusIndicator", typeof(bool), typeof(StatusControl), new PropertyMetadata(false));
Hope this helps!