XAML: setup property from parent view? - c#

I have button custom view:
<UserControl>
...
<Rectangle x:Name="Highlight" Style="{DynamicResource HighlightStyle}"/>
...
<DataTrigger Binding="{Binding Path=IsHighlighted}" Value="true">
<Setter TargetName="Highlight" Property="Opacity" Value="1"/>
</DataTrigger>
...
</UserControl>
And button is used in parent view like next:
<local:MyButton x:Name="Btn1" DataContext="{Binding Path=Btn1}" />
So when I need button to be highlighted I'm doing this from code. Like Btn1.IsHighlighted=true;
But at some point I need to setup this directly from parent XAML. Is it possible?
I.e. on some specific view I don't want Btn1.IsHighlighted to be used. Instead I want something like this:
<local:MyButton x:Name="Btn1" DataContext="{Binding Path=Btn1}" IsHighlighted="true" />

You can register the IsHighlighted as an property of your MyButton class
private static readonly DependencyProperty IsHighlightedProperty = DependencyProperty.Register
(
"IsHighlighted",
typeof(bool),
typeof(MyButton),
new PropertyMetadata((bool)false)
);
public bool IsHighlighted
{
get { return (bool) GetValue(IsHighlightedProperty); }
set { SetValue(IsHighlightedProperty, value); }
}
EDIT adding XAML use
Your MyButton XAML should have something like this
<Rectangle x:Name="Highlight" Width="100">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Style.Triggers>
<DataTrigger Binding="{Binding IsHighlighted}" Value="True">
<Setter Property="Opacity" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
I actually tested Property="Fill" and Value="Green" here. But changed to match your case.
The parent view should have
<local:MyButton x:Name="Btn1" DataContext="{Binding Path=Btn1}" IsHighlighted="true" />

Related

Conditional display of image in c#

I am currently trying to conditionaly display an image. I read quite a bit about valueConverters and triggers, but i strongly believe that there has to be an easier solution for this easy problem.
The XAML:
<Image Source="C:\Users\Niko\Pictures\red.png" IsEnabled="{Binding IsOn}"></Image>
The code behind:
namespace MVVM {
public class Globals
{
int i = 2;
public bool IsOn
{
get
{
if (i == 1 )
return true;
else
return false;
}
}
}
I played around with the integer i to see if the image gets displayed or not. Any advice is greatly apreciated!
Bind the Image's Visibility to IsOn and use the built in BooleanToVisibilityConverter.
<Image Source="C:\Users\Niko\Pictures\red.png" Visibility="{Binding Visibility, Converter={StaticResource BoolToVis}}"/>
Then add the BooleanToVisibilityConverter as a static resource in either the <Window.Resources> for just that window or <Application.Resources> for your whole application.
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
Note that x:Key is the name that you use to reference the converter after StaticResource.
If you don't want to put a Visibility property in your ViewModel and don't want to use converters, you can use a DataTrigger (here I don't have a ViewModel at all, the image is visible if the ToggleButton is checked):
<Image Source="C:\Users\Niko\Pictures\red.png">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Visibility"
Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=tg}"
Value="False">
<Setter Property="Visibility"
Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<ToggleButton Name="tg" Content="Show" HorizontalAlignment="Left" VerticalAlignment="Bottom" />

HasItems Property in TreeViewItem true, even if all children are Visibility = Collapsed

Can you please give me a hint, to make HasItems Property better.
I have a TreeView like this:
<TreeView ItemsSource="{Binding Customers}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="Foreground" Value="Blue" />
</DataTrigger>
</Style.Triggers>
<Setter Property="AutomationProperties.AutomationId" Value="{Binding AutomationId}" />
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Customers}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding FamilyName}" Margin="5,0,0,0" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind:
public ObservableCollection<Customer> Customers { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Customers = new ObservableCollection<Customer>();
var homer = new Customer("Homer", "Simpson");
homer.Customers.Add(new Customer("Bart", "Simpson"));
homer.Customers.Add(new Customer("Lisa", "Simpson"));
homer.Customers.Add(new Customer("Maggie", "Simpson"));
var chief = new Customer("Chief", "Wiggum");
chief.Customers.Add(new Customer("Ralf", "Wiggum"));
Customers.Add(homer);
Customers.Add(chief);
}
The Class Customer implements INotifyPropertyChanged and everything is fine.
As you see here, I have a DataTrigger to change color depending on "HasItems" Property of the TreeViewItem.
The problem is: HasItems is true, even if all children are Hidden or Collapsed.
See here: I made the VISIBILITY of son of "Chief Wiggum" Collapsed. And "Chief Wiggum"-TreeViewItem is still red.
Well, as you could figure out, the fact the items are hidden doesn't mean the tree view has no items.
One possible approach is changing your DataTrigger in the following way:
<DataTrigger Binding="{Binding Items, Converter={StaticResource HasVisibleItemsConverter}, RelativeSource={RelativeSource Self}}" Value="True">
Create a HasVisibleItemsConverter converter class that implements IValueConverter, there you should check if there are any items that are visible - I'll leave that for your own exercise.
Then you create an instance of HasVisibleItemsConverter in the Resources area (either Window.Resources or UserControl.Resources):
<Window.Resources>
<conv:HasVisibleItemsConverter x:Key="HasVisibleItemsConverter" />
</Window.Resources>
And don't forget to add conv="..." in the namespace definition for your Window/UserControl pointing to the assembly and namespace where your converter is.

How to make a WPF resource be recalculated when a trigger's run?

In short: I've got a Style. It uses TemplateBinding a fair bit to make it parametrized instead of repeating myself over and over again. However, when a trigger for that style gets used and a resource gets used in a setter in that trigger, it just doesn't show up! Not even the default value gets shown. Here's a small program that replicates this issue:
TestDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace">
<Style TargetType="Button" x:Key="BtnTest">
<Style.Resources>
<Label Content="{TemplateBinding lcl:TestClass.String}" x:Key="innerLabel"/>
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content" Value="{DynamicResource innerLabel}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
MainWindow.xaml
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace"
Title="Test" Width="500" Height="350">
<Window.Resources>
<ResourceDictionary Source="TestDictionary.xaml"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="Enable/Disable" Click="Click"/>
<Button Grid.Column="1" x:Name="btn" Style="{DynamicResource BtnTest}" lcl:TestClass.String="TESTING"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace MyNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Click(object sender, RoutedEventArgs e)
{
btn.IsEnabled = !btn.IsEnabled;
}
}
public class TestClass
{
public static string GetString(DependencyObject obj)
{
return (string)obj.GetValue(StringProperty);
}
public static void SetString(DependencyObject obj, string value)
{
obj.SetValue(StringProperty, value);
}
public static readonly DependencyProperty StringProperty =
DependencyProperty.RegisterAttached("String", typeof(string), typeof(TestClass), new PropertyMetadata("Default!"));
}
}
Instead of using a TemplateBinding, I also tried this:
{Binding Path=lcl:TestClass.String, RelativeSource={RelativeSource AncestorType={x:Type Button}}}
It still didn't work.
I know I'm probably doing something wrong, but the question is: what is it?
All you really need to make this work is to use RelativeSource in your binding. Since you are setting the attached property on the Button, in your style trigger, you can just bind to the attached property on self:
<Style TargetType="Button" x:Key="BtnTest">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content"
Value="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
One cool thing about using your approach, since Button is a ContentControl, you're attached property can be any object, not just strings.
And to clarify what went wrong in your previous approach -
As others have said, TemplateBinding only works in ControlTemplates. It also only works when the DependencyProperty is defined on the class you are creating the template for (so you can never do a TemplateBinding to Grid.Row for example)
When binding to an attached property, the whole thing needs to be in parentheses, otherwise WPF will try to bind to a property of a property. Otherwise your RelativeSource binding was close!
I think if you want to have a Label inside the Button as the content, it may work (I didn't test that), but it doesn't seem like the best idea, as your Button can host any object you want.
EDIT for more complex example
So, if you need to display more than one dynamic property, I would recommend using a DataTemplate:
<Style TargetType="Button" x:Key="BtnTest">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label Content="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Also, I want to point out that a DataTemplateSelector might be more applicable if you have multiple different criteria for changing the look of the content.
Now I see the details. What you should write before relative source is like:
Binding Path=(lcl:TestClass.String)
Do not forget to add parenthesis.
Your example does not work because TemplateBinding only works in a ControlTemplate. To achieve something akin to a TemplateBinding in Resources you need to do other stuff. Here's an example.
In order for TemplateBinding to work, you need to fix the code a little bit (this is just an example with no resources):
<Style x:Key="BtnTest" TargetType="{x:Type Button}">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="200" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2" Background="{TemplateBinding Background}">
<ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding lcl:TestClass.String}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
Useful links about this topic: Here, and here too.
EDIT:
You can also use the application settings instead of TestClass. Open "Project -> Properties: MyNamespace... -> Settings" and add your settings:
Name--------Type--------Scope--------Value
LabelText---string--------User----------Default
Set the your value for the LabelText in code. For example:
public MainWindow()
{
InitializeComponent();
MyNamespace.Properties.Settings.Default.LabelText = "Testing";
}
And use this ResourceDictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:MyNamespace.Properties"
xmlns:lcl="clr-namespace:MyNamespace">
<Style TargetType="Button" x:Key="BtnTest">
<Style.Resources>
<Label x:Key="innerLabel" Content="{Binding Source={x:Static properties:Settings.Default}, Path=LabelText, Mode=TwoWay}" />
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content" Value="{DynamicResource innerLabel}"/>
</Trigger>
</Style.Triggers>
</Style>

datatrigger on enum to change image

I've got a button with a fixed background image and would like to show a small overlay image on top of it. Which overlay image to chose depends on a dependency property (LapCounterPingStatus) of the according viewmodel.
This is what I got so far:
<Button>
<Grid>
<Image Stretch="None"> <!-- Background Image -->
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="/Images/Pingn.png"/>
</Style>
</Image.Style>
</Image>
<Image Stretch="None" Panel.ZIndex="1"> <!-- Small Overlay Image -->
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=LapCounterPingStatus}" Value="PingStatus.PING_UNKNOWN">
<Setter Property="Source" Value="/Images/RefreshOverlayn.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=LapCounterPingStatus}" Value="PingStatus.PING_FAILURE">
<Setter Property="Source" Value="/Images/ErrorOverlayn.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=LapCounterPingStatus}" Value="PingStatus.PING_SUCCESS">
<Setter Property="Source" Value="/Images/CheckmarkOverlayn.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Grid>
</Button>
Relevant parts of my viewmodel
public class ConfigurationViewModel
{
public enum PingStatus { PING_UNKNOWN, PING_SUCCESS, PING_FAILURE };
public PingStatus LapCounterPingStatus
{
get { return _lapCounterPingStatus; }
set
{
_lapCounterPingStatus = value;
RaisePropertyChanged(LapCounterPingStatusPropertyName);
}
}
}
Right now, no overlay image at all is displayed. What could be wrong?
UPDATE
Trace window of my IDE is showing System.ArgumentException and System.FormatException.
Could the problem source be a unknown type of enumeration PingStatus im the XAML?
You need 2 things to get this working:
1 - Add an xmlns reference in the root element of your XAML file, to the namespace where your Enum is defined:
<UserControl ...
xmlns:my="clr-namespace:YourEnumNamespace;assembly=YourAssembly">
2 - in the Value property of the DataTrigger, use the {x:Static} form:
<DataTrigger Binding="{Binding Path=LapCounterPingStatus}" Value="{x:Static my:PingStatus.PING_UNKNOWN}">
Notice that the Enum type must be prefixed with the xmlns prefix you defined above.
Edit:
If your Enum is declared inside a class you need to use the syntax:
{x:Static namespace:ClassName+EnumName.EnumValue}
for example:
{x:Static my:ConfigurationViewModel+PingStatus.PING_UNKNOWN}
Complete worked example for WPF + MVVM.
Tested on MSVC 2017.
In the view:
<TextBlock Text="Some text to be colored by an enum">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusIcon}" Value="{x:Static my:StatusIcon.Warning}">
<Setter Property="Foreground" Value="Yellow"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusIcon}" Value="{x:Static my:StatusIcon.Error}">
<Setter Property="Foreground" Value="Red}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
If using ReSharper, and if the DataContext is set up properly, there will be intellisense when you hit the . after StatusIcon, i.e. it will show the properties of the enum which are Debug, Info, Warning or Error.
If using ReSharper, it will suggest the following update to the namespace in the header for the XAML file(its
good like that):
xmlns:my="clr-namespace:Class.Path.MyViewModel;assembly=MyAssembly"
And the VieModel:
public enum StatusIcon
{
Debug,
Info,
Warning,
Error
}
public class MyViewModel
{
public StatusIcon StatusIcon { get; }
}
We also use Fody for automated binding.
You can simply set enum value as DataTrigger Value... Tested on MSVC 2017.
<TextBlock Text="Some text to be colored by an enum">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusIcon}" Value="Warning">
<Setter Property="Foreground" Value="Yellow"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusIcon}" Value="Error">
<Setter Property="Foreground" Value="Red}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

WPF Trigger not working, what am i doing wrong?

I have the following code (only relevant snippets)
<ribbon:RibbonWindow .............>
<Grid>
<ribbon:Ribbon>
<ribbon:RibbonToggleButton
x:Name="Button2"
SmallImageSource="Images\SmallIcon.png"
Label="Properties">
<ribbon:RibbonToggleButton.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="SPanel1" Property="UIElement.IsVisible" Value="False"/>
<Setter TargetName="SPanel2" Property="UIElement.IsVisible" Value="True"/>
</Trigger>
</ribbon:RibbonToggleButton.Triggers>
</ribbon:RibbonToggleButton>
</ribbon:Ribbon>
</Grid>
</ribbon:RibbonWindow>
And when i run the application, it hangs up and te debugger pops up. What am i doing wrong?
I have tried setting the trigger on the grid, wrapping everything in a control template and setting a trigger there, same problem!
Edit
I have tried creating a button, and only the tag of trigger causes the Unhandled Exception error.
<Button Content="Button" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="12,27,0,0" Name="TestButton" VerticalAlignment="Top" Width="75" >
<Button.Triggers>
<Trigger></Trigger>
</Button.Triggers>
</Button>
Edit 2
Using Data triggers makes the Unhandled Exception go away but the trigger is not responding:
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Button2, Path=ToggleButton.IsChecked}" Value="True">
<Setter Property="UIElement.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
Edit 3
<ribbon:RibbonToggleButton
x:Name="Button2"
SmallImageSource="Images\SmallIcon.png"
Label="Properties">
</ribbon:RibbonToggleButton>
And The panel to be hidden
<StackPanel
Grid.Row="2"
Grid.Column="1"
x:Name="SPanel1"
Visibility="Visible">
<Label>*Deafult Grid*</Label>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Button2, Path=ToggleButton.IsChecked}" Value="True">
<Setter Property="UIElement.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
And for second StackPanel that i want to show
<StackPanel
Grid.Row="2"
Grid.Column="1"
x:Name="SPanel2"
Visibility="Hidden">
<Label>*Panel 2 *</Label>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Button2, Path=IsChecked}" Value="True">
<Setter Property="UIElement.Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
You need to use DataTrigger, and set the style property of the two panels to the IsChecked propery of the checkbox, here an example
<CheckBox Name="check" Content="Prova" IsChecked="True"> </CheckBox>
<Canvas Name="SPanel1" Background="Blue" Width="100" Height="100">
<Canvas.Style>
<Style TargetType="Canvas">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=check, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
</Canvas>
UIElement.IsVisible is not a mutable property. You'll have to set UIElement.Visibility, which is an enum.
The reason why you get an exception is because FrameworkElement.Triggers only accepts EventTrigger. This means that if you do UIElement.Triggers (Button.Triggers or ribbon:RibbonToggleButton.Triggers), you can only add EventTriggers under that. Style, DataTemplate and ControlTemplate accept all TriggerBase derived classes.
Edit
As Mackho pointed out, you cannot use TargetName under Style. You'd have to accomplish this using data binding.
Assuming SPanel1 is a StackPanel, here's what you can do:
Create a converter
public class BoolToVisibilityConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Somewhere in the root control's resources, add BoolToVisibilityConverter
Add x:Name to your toggle button; lets say you call it "MyToggleButton"
<StackPanel x:Name="SPanel1" ...
Visibility="{Binding IsChecked, ElementName=MyToggleButton, Converter={StaticResource BoolToVisibilityConverter}}" />
And do something very similar for SPanel2.

Categories

Resources