Wpf Xaml change canvas property "Path.Fill" [duplicate] - c#

I'm attempting to use a data-trigger on a style to change a property.
In compliance with the "Minimal, Complete and Verifiable Example" requirements...
To reproduce, first create a WPF application in Visual Studio.
Within the App.xaml.cs :
using System.ComponentModel;
using System.Windows;
namespace Foo{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application, INotifyPropertyChanged {
private bool _clicked;
public bool Clicked {
get { return this._clicked; }
set {
this._clicked = value;
this.PropertyChanged?.Invoke(
this, new PropertyChangedEventArgs( "Clicked" ) );
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Within the MainWindow.xaml :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:lib="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:Foo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d" x:Class="Foo.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<lib:Boolean x:Key="True">True</lib:Boolean>
</Window.Resources>
<Grid>
<Button x:Name="button" Click="button_Click">
<Viewbox>
<TextBlock Text="Unclicked">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger
Binding="{Binding
Clicked,
Source={x:Static Application.Current}}"
Value="{StaticResource True}">
<Setter Property="Text" Value="Clicked" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Viewbox>
</Button>
</Grid>
</Window>
Within the MainWindow.xaml.cs -
using System.Windows;
namespace Foo{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow( ) {
InitializeComponent( );
}
private void button_Click( object sender, RoutedEventArgs e ) {
( Application.Current as App ).Clicked = !( Application.Current as App ).Clicked;
}
}
}
As a side note - I tried setting the value of the data trigger to just "True", and that also did not work ( the trigger did not catch, and text did not change based on setting the property to a new value ).
So why is the data-trigger not catching or working here? ( Either with the static resource or the literal value )? Even more relevant - why am I getting this error? The "After a 'DataTrigger' is in use (sealed), it cannot be modified" error? And what is the proper method of accomplishing what I am trying to do here? ( Preferably still using a data-trigger and not a converter, since I do need to switch between two values ).

The local value assigned to the TextBlock's Text property has higher precedence than the value provided by the Setter in the DataTrigger. See Dependency Property Value Precedence for details.
Set the initial Text value by another Setter:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="Unclicked"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Clicked,
Source={x:Static Application.Current}}"
Value="{StaticResource True}">
<Setter Property="Text" Value="Clicked" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
The error message you see when you use the Boolean resource is just the XAML designer complaining. There is no error at runtime.

Related

How to bind SelectionChanged Event to Visibility Property on other Element in XAML Only

Given is an ComboBox after SelectionChanged should become an TextBlock visible. I build this functionality using ViemModel.
View:
<ComboBox SelectionChanged="{mvvmHelper:EventBinding OnSelectionChanged}" />
<TextBlock Visibility="{Binding LanguageChanged, Converter={StaticResource BooleanVisibilityConverter}}"/>
ViewModel:
bool LanguageChanged = false;
void OnSelectionChanged() => LanguageChanged = true;
I looking for a elegant solution done in XAML only
What i try so far:
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsDropDownOpen, ElementName=Box, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
I guess I have to use Storyboard
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<EventTrigger RoutedEvent="SelectionChanged">
<BeginStoryboard>
<Storyboard>
???
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
the other option would be System.Windows.Interactivity but this is not available in WpfCore 3.1
You have several good options here.
Since the last solution, which uses a DataTrigger, is the most flexible, as it allows to trigger on certain states of the ComboBox.SelectedItem, I recommend to implement it to solve your problem. It's also a XAML only solution and doesn't require an extra property like LanguageChanged.
Animate a trigger property
In order to animate a property like LanguageChanged, the property must be a DependencyProperty. The first example therefore implements LanguageChanged as a DependencyProperty of MainWindow:
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static readonly DependencyProperty LanguageChangedProperty = DependencyProperty.Register(
"LanguageChanged",
typeof(bool),
typeof(MainWindow),
new PropertyMetadata(default(bool)));
public bool LanguageChanged
{
get => (bool) GetValue(MainWindow.LanguageChangedProperty);
set => SetValue(MainWindow.LanguageChangedProperty, value);
}
}
MainWindow.xaml
<Window x:Name="Window">
<StackPanel>
<TextBlock Text="Invisible"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=MainWindow}, Path=LanguageChanged, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ComboBox>
<ComboBox.Triggers>
<EventTrigger RoutedEvent="ComboBox.SelectionChanged">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="Window"
Storyboard.TargetProperty="LanguageChanged">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ComboBox.Triggers>
</ComboBox>
</StackPanel>
</Window>
Animate the target control directly
If the control you wish to toggle the visibility of is in the same scope as the triggering control you can animate the Visibility directly:
MainWindow.xaml
<Window x:Name="Window">
<StackPanel>
<TextBlock x:Name="InvisibleTextBlock"
Text="Invisible"
Visibility="Hidden" />
<ComboBox>
<ComboBox.Triggers>
<EventTrigger RoutedEvent="ComboBox.SelectionChanged">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvisibleTextBlock"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ComboBox.Triggers>
</ComboBox>
</StackPanel>
</Window>
Implement IValueConverter
In case you wish to add more conditions to the trigger like which value was selected, you should bind the TextBlock.Visibility to ComboBox.SelectedItem and use a IValueConverter to decide whether to return Visibility.Visible or Visibilty.Hidden based on the current selected item:
MainWindow.xaml
<Window x:Name="Window">
<Window.Resources>
<!-- TODO::Implement IValueConverter -->
<SelectedItemToVisibilityConverter x:Key="SelectedItemToVisibilityConverter" />
</Window.Resources>
<StackPanel>
<TextBlock Text="Invisible"
Visibility="{Binding ElementName=LanguageSelector, Path=SelectedItem, Converter={StaticResource SelectedItemToVisibilityConverter}}" />
<ComboBox x:Name="LanguageSelector" />
</StackPanel>
</Window>
Implement a DataTrigger on the TextBlock
In case you wish to add more conditions to the trigger like which value was selected, you could also add a DataTrigger to the TetxtBlock which triggers on one or more properties of the ComboBox.SelectedItem. You then have to cast the SelectedItem to the actual type of the underlying ComboBox items in order to reference the item's properties in the binding path.
The following example casts the SelectedItem to an imaginary type LanguageItem to access the LanguageItem.LanguageName property, to trigger on a specific selected language:
MainWindow.xaml
<Window x:Name="Window">
<StackPanel>
<TextBlock x:Name="InvisibleTextBlock" Text="Invisible">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=LanguageSelector, Path=SelectedItem.(LanguageItem.LanguageName)}"
Value="English">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<ComboBox x:Name="LanguageSelector" />
</StackPanel>
</Window>
I feel like #BionicCode has given quite the comprehensive answer, but I'll add my 2ยข.
The best solution that meets your requirements I think is the style trigger.
I see that Bionic included that, but here is an MCVE:
<Window x:Class="project-name.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="120" Width="300">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="15,15,0,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Language: " VerticalAlignment="Center"/>
<ComboBox x:Name="LanguageCB" HorizontalAlignment="Left" SelectedIndex="0">
<ComboBoxItem Content="None ?"/>
<ComboBoxItem Content="English"/>
</ComboBox>
</StackPanel>
<Border Margin="0,10,0,0" BorderThickness="1" BorderBrush="Black" Padding="2">
<TextBlock Text="Becomes visible when "LanguageCB" changes selection">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=LanguageCB, Path=SelectedIndex}" Value="1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</StackPanel>
</Window>
However... If you are really doing localization in your application and not just using this as an example, then I think there is a better solution.
First take a few minutes to read about WPF Globalization and Localization.
Then add at least 1 language resource file to your project's properties (for example 'Resources.ja-JP.resx', and don't forget to mark your Resources.resx file to public. Put some localized strings in these .resx files.
Then bind your TextBlock's text to the property:
<TextBlock Text="{Binding Path=ResourceName, Source={StaticResource Resources}}"/>
Next you need some code to handle switching cultures.
There a lots of options here, but I'll include some code I've used in the past.
CultureResources.cs
namespace Multi_Language_Base_App.Cultures
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Diagnostics;
using System.Windows.Data;
/// <summary>
/// Wraps up XAML access to instance of Properties.Resources,
/// list of available cultures, and method to change culture </summary>
public class CultureResources
{
private static ObjectDataProvider provider;
public static event EventHandler<EventArgs> CultureUpdateEvent;
//only fetch installed cultures once
private static bool bFoundInstalledCultures = false;
private static List<CultureInfo> pSupportedCultures = new List<CultureInfo>();
/// <summary>
/// List of available cultures, enumerated at startup
/// </summary>
public static List<CultureInfo> SupportedCultures
{
get { return pSupportedCultures; }
}
public CultureResources()
{
if (!bFoundInstalledCultures)
{
//determine which cultures are available to this application
Debug.WriteLine("Get Installed cultures:");
CultureInfo tCulture = new CultureInfo("");
foreach (string dir in Directory.GetDirectories(AppDomain.CurrentDomain.BaseDirectory))
{
try
{
//see if this directory corresponds to a valid culture name
DirectoryInfo dirinfo = new DirectoryInfo(dir);
tCulture = CultureInfo.GetCultureInfo(dirinfo.Name);
//determine if a resources dll exists in this directory that matches the executable name
string exe = System.Reflection.Assembly.GetExecutingAssembly().Location;
if (dirinfo.GetFiles(Path.GetFileNameWithoutExtension(exe) + ".resources.dll").Length > 0)
{
pSupportedCultures.Add(tCulture);
Debug.WriteLine(string.Format(" Found Culture: {0} [{1}]", tCulture.DisplayName, tCulture.Name));
}
}
catch (ArgumentException) //ignore exceptions generated for any unrelated directories in the bin folder
{
}
}
bFoundInstalledCultures = true;
}
}
/// <summary>
/// The Resources ObjectDataProvider uses this method to get
/// an instance of the _This Application Namespace_.Properties.Resources class
/// </summary>
public Properties.Resources GetResourceInstance()
{
return new Properties.Resources();
}
public static ObjectDataProvider ResourceProvider
{
get
{
if (provider == null)
provider = (ObjectDataProvider)App.Current.FindResource("Resources");
return provider;
}
}
/// <summary>
/// Change the current culture used in the application.
/// If the desired culture is available all localized elements are updated.
/// </summary>
/// <param name="culture">Culture to change to</param>
public static void ChangeCulture(CultureInfo culture)
{
// Remain on the current culture if the desired culture cannot be found
// - otherwise it would revert to the default resources set, which may or may not be desired.
if (pSupportedCultures.Contains(culture))
{
Properties.Resources.Culture = culture;
ResourceProvider.Refresh();
RaiseCultureUpdateEvent(null, new EventArgs());
Debug.WriteLine(string.Format("Culture changed to [{0}].", culture.NativeName));
}
else
{
Debug.WriteLine(string.Format("Culture [{0}] not available", culture));
}
}
private static void RaiseCultureUpdateEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handleit = CultureUpdateEvent;
CultureUpdateEvent?.Invoke(sender, e);
}
}
}
The last piece of the puzzle should be a way to provide access to the culture resources from xaml. This is done using an ObjectDataProvider.
You can put this directly in App.xaml or a separate file and reference it in App.xaml.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Cultures="clr-namespace:Multi_Language_Base_App.Cultures">
<!-- Contains the current instance of the ProjectName.Properties.Resources class.
Used in bindings to get localized strings and automatic updates when the culture is updated -->
<ObjectDataProvider x:Key="Resources"
ObjectType="{x:Type Cultures:CultureResources}"
MethodName="GetResourceInstance"/>
<!-- Provides access to list of currently available cultures -->
<ObjectDataProvider x:Key="CultureResourcesDS"
ObjectType="{x:Type Cultures:CultureResources}"/>
</ResourceDictionary>
When you do this, your string bindings can automatically match the system culture from the get-go (or else end up as the defaults in your generic resources). Also the user can switch the culture on the fly.
In your example, the ComboBox SelectionChanged event would be used as a starting point to change the culture like this:
CultureInfo CultureJapanese = new CultureInfo("ja-JP");
Cultures.CultureResources.ChangeCulture(CultureJapanese);
I prefer to use commanding to get this done, but it's up to you.

How to hide tab control when item source list is empty

I have a WPF .NET Core application with a TabControl bound to an ObservableCollection for the TabItems. I would like the TabControl to be hidden when the ObservableCollection becomes empty, and I would like to display another panel in its place. Then, when an item is added back to the ObservableCollection, I want the alternate panel hidden and the TabControl reshown. How would I accomplish this, hopefully in XAML with as little code-behind as possible? I know I can do it in code-behind.
Below is the key section of the app. I have hidden the TabControl, and included a Border control to represent the Panel that I will show when the TabControl is hidden.
<Window x:Class="TabTest.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:TabTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Border Name="emptyTabPanel" Grid.Row="1" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"
BorderThickness="1,1,1,1" Margin="5,0,5,5" Visibility="Hidden">
</Border>
<TabControl Name="MainTabControl" Visibility="Visible">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Header}" MinWidth="60"/>
<Button BorderThickness="0" Background="Transparent" Height="16" Width="15" Margin="15,2,0,0">
<Image Source="images/close.png" Height="8"/>
</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
namespace TabTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<TabEntry> tabEntries;
public MainWindow()
{
InitializeComponent();
tabEntries = new ObservableCollection<TabEntry>();
MainTabControl.ItemsSource = tabEntries;
for (int i = 1; i <= 5; i++)
{
tabEntries.Add(new TabEntry { Header = "tab " + i });
}
}
}
public class TabEntry
{
public string Header { get; set; }
}
}
All ItemsControls provide a HasItems property that you can use in a Trigger. In contrast to a DataTrigger on ItemsSource.Count this also works when ItemsSource is not set at all.
<TabControl ...>
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.Style>
...
</TabControl>
You can do it with a DataTriger in a Style. Note that you need to remove Visibility="Visible" or the Setter won't be able to change it.
<TabControl Name="MainTabControl" Background="Red">
<TabControl.Style>
<Style TargetType="TabControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ItemsSource.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<!-- and so on -->
You need a proper view model for this, rather than binding the tabs directly to the collection. That view model would include a HasItems property which you'll bind your TabControl visibility to, and an inverse property - say IsEmpty - which you'll bind the panel's visibility to.
Bind ObservableCollection's events to listen to changes in item count and raise PropertyChanged events for your view model appropriately.

WPF: User Control access Style via Dependency Property

I have created a Style for a Button with an Image:
<Style x:Key="StyleButtonBase" TargetType="Button">
<Style.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Sizes/Sizes.xaml" />
<ResourceDictionary Source="../Colors/Brushes.xaml" />
<ResourceDictionary Source="../Fonts/Fonts.xaml" />
<ResourceDictionary Source="../Images/Images.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Style.Resources>
<Setter Property="Background" Value="{StaticResource BrushButtonActive}" />
<Setter Property="Foreground" Value="{StaticResource BrushForegroundLight}" />
<Setter Property="FontFamily" Value="{StaticResource FontFamilyDefault}" />
<Setter Property="FontSize" Value="{StaticResource DoubleFontSizeStandard}" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{StaticResource BrushBorder}"
BorderThickness="{StaticResource ThicknessBorder}"
CornerRadius="{StaticResource CornerRadius}">
<Image Source="{StaticResource IconIcon}" Stretch="None" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource BrushButtonPressed}" />
</Trigger>
</Style.Triggers>
</Style>
Now I Want to create a User-Control which only consists of a Button with this style and a Dependency Property to set the Button Image. The XAML part of my user control looks like this:
<UserControl
x:Class="HH.HMI.ToolSuite.ResourceLib.Controls.ButtonSmall">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/Buttons.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Button Style="{StaticResource StyleButtonBase}" Width="{StaticResource DoubleWidthButtonSmall}" Height="{StaticResource DoubleHeightControls}">
</Button></UserControl>
The code behind of my user-control looks like this:
public partial class ButtonSmall : UserControl, INotifyPropertyChanged
{
public ButtonSmall()
{
InitializeComponent();
}
public static readonly DependencyProperty ButtonImageProperty
= DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(TextOutput), new PropertyMetadata(null, OnButtonImagePropertyChanged));
private static void OnButtonImagePropertyChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
ButtonSmall temp = dependencyObject as ButtonSmall;
temp.OnPropertyChanged("ButtonImage");
temp.OnButtonImagePropertyChanged(e);
}
private void OnButtonImagePropertyChanged(DependencyPropertyChangedEventArgs e)
{
ButtonSmallImage.Source = ButtonImageSource;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ImageSource ButtonImageSource
{
get { return (ImageSource)GetValue(ButtonImageProperty); }
set { SetValue(ButtonImageProperty, value); }
}
}
In my other user-controls i usually access an element in the user control itself like:
xamlname.text = text
Now i haven't a named element in my xaml code of the user-control. Instead i have the named element in the style, which i reference in the user control. How can access this throug my code behind?
If I were you I'd subclass Button and create a new class (just a .cs file) like so:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MyProject
{
public class IconButton : Button
{
public static readonly DependencyProperty ButtonImageProperty = DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(IconButton),
new FrameworkPropertyMetadata(new BitmapImage(), FrameworkPropertyMetadataOptions.AffectsRender));
public ImageSource ButtonImage
{
get { return (ImageSource)GetValue(ButtonImageProperty); }
set { SetValue(ButtonImageProperty, value); }
}
}
}
This means you can now reference the property. Otherwise, because your button is just a regular button (with only a regular button's properties; no image), your style doesn't know about your new image property which it expects a Button to have. Don't forget to update your style's TargetType's to point to IconButton.
If you place your style in the resources section of your User Control, you can set the button style like so:
<UserControl x:Class="MyProject.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myclass="clr-namespace:MyProject">
<UserControl.Resources>
<!-- your style here -->
</UserControl.Resources>
<myclass:IconButton Style="{StaticResource StyleButtonBase}/>
</UserControl>
(The xmlns 'myclass' must be replaced to refer to the namespace your custom button is in!)
Also, if you remove the x:Key property from the style, it will apply to all buttons in scope, meaning you can omit setting it explicitly. This may be handy if you locate it in a shared ResourceDictionary (if you're building a library of custom controls for example) (if you do this, you will need to combine this resource dictionary in your App.xaml.cs file). If you end up doing that and you discover your UserControl doesn't have any especial functionality beyond wrapping an IconButton, you can of course omit it entirely and just use IconButtons directly in other controls. Your style declares how your IconButton looks, and your IconButton class ensures that the resources (your image) your style expects are there when it looks for them at runtime, so as long as your style is in scope, you're good to go.
If the Style is defined in Application.Resources in App.xaml, or in a resource dictionary that's merged into Application.Resources in App.xaml, you can just reference it in the user control via StaticResource. If it's in another resource dictionary, you'll have to merge that one into UserControl.Resources.
Or you can put it directly in UserControl.Resources as TernaryTopiary suggests, if it won't be needed elsewhere.
As for the image source property, you could write a Button subclass as Ternary suggests, or you could write an attached property (see below). In XAML, when you customize controls, first you try to do it with regular attributes; then you try to restyle the thing. Then you escalate to replacing the control template, and that won't quite do the job, you consider attached properties/behaviors. Only if all else fails to do you resort to subclassing. You should know how to do it, but you should also learn the other ways of doing things.
In this case, there's a somewhat quick and dirty way to do it that's consistent with the correct XAML way of doing things: The Content property of the button is going unused, and its declared type is Object, so we can just use that. Since we're using a binding to pass in the image source, you can get rid of that PropertyChanged handler on ButtonImageSource.
<UserControl
...>
<!-- ... -->
<Button
Content="{Binding ButtonImageSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
Style="{StaticResource StyleButtonBase}"
Width="{StaticResource DoubleWidthButtonSmall}"
Height="{StaticResource DoubleHeightControls}"
/>
And make the following change in the control template in StyleButtonBase:
<ControlTemplate TargetType="Button">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{StaticResource BrushBorder}"
BorderThickness="{StaticResource ThicknessBorder}"
CornerRadius="{StaticResource CornerRadius}"
>
<!-- Now we'll find the image source in the button's Content --->
<Image
Source="{TemplateBinding Content}"
Stretch="None"
/>
</Border>
</ControlTemplate>
Attached Property
Using Content for this is a very mild abuse of WPF: You shouldn't really be repurposing properties. Content is universally understood in WPF to mean "any arbitrary content", not "any ImageSource".
So it's a little more "correct" to use an attached property, and it's not a lot more work. Here's how that would look.
We'll define the attached property in a separate static class, because I can't think of a good name for it other than ButtonImageSource, which you're already using in SmallButton:
public static class ButtonHelper
{
#region ButtonHelper.ButtonImageSource Attached Property
public static ImageSource GetButtonImageSource(Button obj)
{
return (ImageSource)obj.GetValue(ButtonImageSourceProperty);
}
public static void SetButtonImageSource(Button obj, ImageSource value)
{
obj.SetValue(ButtonImageSourceProperty, value);
}
public static readonly DependencyProperty ButtonImageSourceProperty =
DependencyProperty.RegisterAttached("ButtonImageSource", typeof(ImageSource), typeof(ButtonHelper),
new PropertyMetadata(null));
#endregion ButtonHelper.ButtonImageSource Attached Property
}
In the XAML, the user control uses this attached property instead of Content:
<Button
Style="{StaticResource StyleButtonBase}"
local:ButtonHelper.ButtonImageSource="{Binding ButtonImageSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
And the control template does likewise:
<ControlTemplate TargetType="Button">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4"
>
<Image
Source="{TemplateBinding local:ButtonHelper.ButtonImageSource}"
Stretch="None"
/>
</Border>
</ControlTemplate>
All the attached property does is give us a property that isn't named Content, and which is strongly typed as ImageSource, which we can use to pass in that image source.
Another thing: Maybe this was an error that crept in when you simplified your code for the question, but you're passing typeof(TextOutput) to DependencyProperty.Register() where you should be passing typeof(ButtonSmall). More importantly, you've got two names for what should be a single property: ButtonImage and ButtonImageSource.
public static readonly DependencyProperty ButtonImageSourceProperty
= DependencyProperty.Register(
"ButtonImageSource",
typeof(ImageSource),
// Should be ButtonSmall, not TextOutput
typeof(ButtonSmall),
new PropertyMetadata(null));
public ImageSource ButtonImageSource
{
get { return (ImageSource)GetValue(ButtonImageSourceProperty); }
set { SetValue(ButtonImageSourceProperty, value); }
}
Incidentally, in your Style, it would be better practice to use TemplateBinding for BorderBrush and BorderThickness, and set the defaults in Style setters, the way you did with Background.

C# Wpf setting toggle button from behind code using INotifyPropertyChanged

I have a simple toggle button which works perfectly well . I can click on the toggle button and change the image that it shows . What i now want to do is the same thing from the code behind . Found a link that is similar
EDIT : This is what i want to do
I read up on the following thread that tells exactly what i need to do
WPF ToggleButton.IsChecked binding does not work
Programmatically my code does not seem to have any effect . if i click on the UI it works but i really wanna change state from within the program . The below program is just a prototype .
I cant figure out whats wrong in my XAML or code . Finnally decided to paste all of it as it a test program !
Xaml :
<Window x:Class="ToggleButtonImageChange.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ToggleButtonImageChange"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Image Source="secured.jpg"
x:Key="MyImage1" />
<Image Source="unsecured.jpg"
x:Key="MyImage2" />
<Style TargetType="{x:Type ToggleButton}"
x:Key="MyToggleButtonStyle">
<Setter Property="Content"
Value="{DynamicResource MyImage2}" />
<Style.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter Property="Content"
Value="{DynamicResource MyImage2}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ToggleButton Style="{StaticResource MyToggleButtonStyle}" Name="tgbtn" Margin="0,29,0,139" IsChecked="{Binding Path=isAdmin, Mode=TwoWay}"/>
</Grid>
</Window>
Code behind :
namespace ToggleButtonImageChange
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window,INotifyPropertyChanged
{
bool _isAdmin;
public MainWindow()
{
InitializeComponent();
isAdmin = true;
OnPropertyChanged("isAdmin");
}
public bool isAdmin
{
get
{
return _isAdmin;
}
set
{
_isAdmin = value;
OnPropertyChanged("isAdmin");
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I went into the debugger and saw that even though i set isAdmin to true the button isChecked remains false and hence the incorrect image is displayed . I cant quite understand what wrong did do & how to get the isChecked changed through code .
Try to change the xaml file to this:
<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"
x:Name="TestWindow">
<Window.Resources>
<Image Source="secured.png" x:Key="MyImage1" />
<Image Source="unsecured.png" x:Key="MyImage2" />
<Style TargetType="{x:Type ToggleButton}" x:Key="MyToggleButtonStyle">
<Setter Property="Content" Value="{DynamicResource MyImage2}" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="{DynamicResource MyImage1}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ToggleButton x:Name="tgbtn"
Margin="0,29,0,139"
Style="{StaticResource MyToggleButtonStyle}"
IsChecked="{Binding Path=isAdmin, Mode=TwoWay, ElementName=TestWindow}"/>
</Grid>
</Window>
Notice the default Content value use MyImage2, but the trigger set it to MyImage1 - they just need to be different images.
Also notice the x:Name="TestWindow" that I've add to root window element - it is used later in binding:
{Binding Path=isAdmin, Mode=TwoWay, ElementName=TestWindow}
This is basically all what is required to change to make it work as you expect, I believe.
Also you can leave you constructor in code behind like this, but this is optional changes:
public MainWindow()
{
InitializeComponent();
isAdmin = true;
}
Hope that helps.

Prevent IValueConverter from setting a local value

Short version:
WPF seems to always set a local value when using an IValueConverter in a binding, even if that converter returns Binding.DoNothing.
My question is: What do I have to return or do to tell WPF to use the inherited value?
Please note: I don't want to use DataTriggers as this would bloat up my code significantly because I would need one data trigger along with a converter for every color my current converter returns.
Long version with reproduction:
Imagine the following scenario:
I have a Button in which a TextBlock is located. There exists a style for the Button that sets the Foreground property. This value is inherited by the TextBlock.
Now I want to create a value converter that converts the value of the TextBlock to a Brush to be used as the Foreground - but only in some cases. In the cases in which I don't want to set a special color, I return Binding.DoNothing. My understanding was that this would make the TextBlock to continue to use the inherited value.
Unfortunatelly, my understanding was not correct. Even when returning Binding.DoNothing a local value is set. This has been verified with Snoop.
The problem can be easily reproduced with this simple example:
XAML:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<WpfApplication1:DummyConverter x:Key="DummyConverter" />
<Style TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground"
Value="{Binding Path=Text, RelativeSource={RelativeSource Self}, Converter={StaticResource DummyConverter}}" />
</Style>
</Window.Resources>
<StackPanel>
<Button><TextBlock>Text1</TextBlock></Button>
<Button><TextBlock>Text2</TextBlock></Button>
</StackPanel>
</Window>
Converter:
public class DummyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.ToString() == "Text2")
return Brushes.Cyan;
return Binding.DoNothing;
}
}
As you can see, the first button has a black text instead of red. If you remove the style for TextBlock both buttons will have the correct red text.
Question:
What do I have to do to prevent this? Is there some value to return that tells the engine to continue using the inherited value?
To answer your question: according to this thread, no. As soon as you give the TextBlock a style setter (#4), any value returned will override inherited properties (#7).
Instead, you could create a MultiBinding like so:
public class DummyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0].ToString() == "Text2")
return Brushes.Cyan;
return values[1];
}
}
<Window x:Class="Spritefire.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Spritefire"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:DummyConverter x:Key="DummyConverter" />
<Style TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground">
<Setter.Value>
<MultiBinding Converter="{StaticResource DummyConverter}">
<Binding Path="Text" RelativeSource="{RelativeSource Self}" />
<Binding Path="Foreground" ElementName="ExampleButton" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<Button x:Name="ExampleButton">
<TextBlock>Text1</TextBlock>
</Button>
<Button>
<TextBlock>Text2</TextBlock>
</Button>
</StackPanel>
</Window>
I've got it working but this solution isn't nice and smells a little... Nevertheless I'll post it.
Declare custom attached property like this:
public static class CustomForeground
{
public static readonly DependencyProperty CustomForegroundProperty = DependencyProperty.RegisterAttached(
"CustomForeground",
typeof(Brush),
typeof(CustomForeground),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender, OnChange));
public static void SetCustomForeground(UIElement element, Brush value)
{
element.SetValue(CustomForegroundProperty, value);
}
public static void OnChange(DependencyObject d, DependencyPropertyChangedEventArgs arg)
{
if (arg.NewValue != null)
d.SetValue(TextBlock.ForegroundProperty, arg.NewValue);
}
}
and text box style:
<Style TargetType="{x:Type TextBlock}">
<Setter Property="WpfApplication1:CustomForeground.CustomForeground"
Value="{Binding Path=Text, RelativeSource={RelativeSource Self}, Converter={StaticResource DummyConverter}}" />
</Style>
This is the only thing that comes to my mind at the moment and it will work assuming that text of you buttons is not changing. If it is you would need to remember default value of Foreground in another attached property and set it in else statement.

Categories

Resources