WPF - Binding data template - wpf

I'm trying to bind <DataTemplate> on Telerik RadDiagram ShapeStyle:
<telerik:RadDiagram x:Name="Diagram"
ShapeStyle="{StaticResource NodeStyle}"
GraphSource="{Binding GraphSource}"/>
This style looks like this:
<Grid.Resources>
<Style x:Key="NodeStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<MultiBinding Converter="{StaticResource StyleConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}"/>
<Binding Path=""/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
...
</Grid.Resources>
StyleConverter:
class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
if (values[0] is BilledMsisdnViewModel)
return targetElement.FindResource("BilledMsisdnControl");
else if(values[0] is BilledImeiViewModel)
return targetElement.FindResource("ImeiControl");
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Those resources (BilledMsisdnControl and ImeiControl) are defined in <UserControl.Resources>:
<UserControl.Resources>
<ResourceDictionary>
<local:BilledMsisdnControl x:Key="BilledMsisdnControl"/>
<local:ImeiControl x:Key="ImeiControl"/>
<local:StyleConverter x:Key="StyleConverter"/>
</ResourceDictionary>
</UserControl.Resources>
And they are created by me user controls.
So my goal here is according to binded view model type select user controll (this part works) and then use it as ContentTemplate (this don't work). I'm getting just object namespace nad name for ImeiControl or BilledMsisdnControl in diagram node.
How can I fix this?

According to the documentation you should use style selector
public class NodeStyleSelector : StyleSelector
{
public Style BilledMsisdnStyle { get; set; }
public Style BilledImeiStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is BilledMsisdnViewModel)
return BilledMsisdnStyle;
else if (item is BilledImeiViewModel)
return BilledImeiStyle;
else return base.SelectStyle(item, container);
}
}
Define styles with ContentTemplate. Inside ContentTemplate add your user controls.
<Style x:Key="BilledMsisdnStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<local:BilledMsisdnControl/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BilledImeiStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<local:ImeiControl/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Add shape style selector
<styleselectors:NodeStyleSelector x:Key="CustomShapeStyleSelector"
BilledMsisdnStyle="{StaticResource BilledMsisdnStyle}"
BilledImeiStyle="{StaticResource BilledImeiStyle}" />
Use ShapeStyleSelector for your RadDiagram.
<telerik:RadDiagram x:Name="Diagram"
ShapeStyleSelector="{StaticResource CustomShapeStyleSelecto}"
GraphSource="{Binding GraphSource}"/>

Related

WPF RelativeSource UpdateSourceTrigger=PropertyChanged not worked

I'm using this DataTrigger:
<Window x:Class="_11_5_Style_demo4_DataTrigger.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:_11_5_Style_demo4_DataTrigger"
mc:Ignorable="d"
Title="MainWindow" Height="130" Width="300">
<Window.Resources>
<local:L2BConverter x:Key="cvtr"/>
<!--TextBox DataTrigger-->
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={x:Static RelativeSource.Self},
Path=Text.Length,
Converter={StaticResource cvtr},
UpdateSourceTrigger=PropertyChanged}"
Value="false">
<DataTrigger.Setters>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="1"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Margin="5"/>
<TextBox Margin="5,0"/>
<TextBox Margin="5"/>
</StackPanel>
</Window>
I expected that the TextBox will have the red border when typed more than 6 characters, and UpdateSourceTrigger=PropertyChanged just not work when there are more than 6 characters in the TextBox. It updated only when lost focus.
Here is the Converter:
public class L2BConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int textLength = (int)value;
return textLength < 6 ? true : false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I've googled, but didn't find relative problem. Can anybody explains why this not work, am I not using it right?
Your trigger is working correctly, but your expectations of the result of its work are not correct. The trigger correctly sets the border brush, but the problem is that the color of the border while the TextBox has focus is not taken from the TextBox.BorderBrush brush, but from the TextBox Template constant. And you can't change it with a trigger. You need to change the template of the TextBox itself or apply another way to solve your problem.
You can make sure that the trigger works correctly, for example, by changing the frame thickness:
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={x:Static RelativeSource.Self},
Path=Text.Length,
Converter={StaticResource cvtr}}"
Value="false">
<DataTrigger.Setters>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="10"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
Another way to implement such validation is to use a ValidationRule. But its use is only possible in a binding that you don't have. You can use a little "voodoo magic" for this:
public class LengthValidate : ValidationRule
{
public LengthValidate() :base(ValidationStep.UpdatedValue, true) { }
public int LengthLimit { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int limit = LengthLimit;
if(value is BindingExpression expression)
{
value = expression.GetSourceValue();
}
if (value is not string text)
{
text= value?.ToString()?? string.Empty;
}
return text.Length <= limit
? ValidationResult.ValidResult
: new ValidationResult(false, $"String length exceeds limit={limit}.");
}
}
The GetSourceValue method from the BindingExpressionHelper class is used.
Style with this rule:
<Style TargetType="TextBox">
<Setter Property="Tag">
<Setter.Value>
<Binding Path="Text" RelativeSource="{RelativeSource Self}">
<Binding.ValidationRules>
<local:LengthValidate LengthLimit="6"/>
</Binding.ValidationRules>
</Binding>
</Setter.Value>
</Setter>
</Style>

Element binding not updating when dependency property changed

I have a custom button control which has a dependency property "IsRequired"
public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.RegisterAttached(nameof(IsRequired), typeof(bool), typeof(RequiredButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
public bool IsRequired
{
get { return (bool)GetValue(IsRequiredProperty); }
set { SetValue(IsRequiredProperty, value); }
}
And implements an interface
public interface IRequiredControl
{
bool IsEnabled { get; }
bool IsRequired { get; }
}
And I have a converter that uses this interface
<sharedConverters:IsRequiredToImageConverter x:Key="IsRequiredToImageConverter"
DisabledImage="{StaticResource DisabledDrawing}"
NormalImage="{StaticResource NormalDrawing}"
RequiredImage="{StaticResource IsRequiredDrawing}" />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IRequiredControl requiredControl)
{
return !requiredControl.IsEnabled ? DisabledImage : requiredControl.IsRequired ? RequiredImage : NormalImage;
}
return DependencyProperty.UnsetValue;
}
I use the converter and bind the image source to the control, as seen below.
<DataTemplate x:Key="RightSideAddTemplate">
<sharedControls:RequiredButton x:Name="addButton"
Command="{x:Static commands:AddCommand}"
IsRequired="{Binding IsButtonRequired}">
<Image Source="{Binding ElementName=addButton, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IsRequiredToImageConverter}}" />
</sharedControls:RequiredButton>
</DataTemplate>
The issue occurs here where the converter is never called when the "IsButtonRequired" viewmodel property is changed. When I set the "IsRequired" of the "AddButton" explicitly to true it works correctly. How can I make the converter update on a change to the "IsRequired" property?
Note I have another solution working where I use a multivalue converter, but I would prefer to get element binding solution working, because it requires much less xaml.
<Image Style="{StaticResource AddImageButtonStyle}">
<Image.Source>
<MultiBinding Converter="{StaticResource IsRequiredToImageMultiConverter}">
<MultiBinding.Bindings>
<Binding ElementName="addButton" Path="IsEnabled" />
<Binding Path="IsButtonRequired" />
</MultiBinding.Bindings>
</MultiBinding>
</Image.Source>
</Image>
The Binding expression
<Image Source="{Binding ElementName=addButton,
Converter={StaticResource IsRequiredToImageConverter}}" />
binds directly to the RequiredButton control, and is not supposed to be triggered when a property of the control changes.
The proper way to implement this is a MultiBinding on both the IsEnabled and IsRequired properties of the control:
<MultiBinding Converter="{StaticResource IsRequiredToImageMultiConverter}">
<MultiBinding.Bindings>
<Binding ElementName="addButton" Path="IsEnabled"/>
<Binding ElementName="addButton" Path="IsRequired"/>
</MultiBinding.Bindings>
</MultiBinding>
The multi-value converter would have to test two boolean values.
Alternatively to using a MultiBinding, a Style with a set of MultiTriggers would also work:
<Style TargetType="sharedControls:RequiredButton">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource DisabledDrawing}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="true" />
<Condition Property="IsRequired" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource NormalDrawing}"/>
</Setter.Value>
</Setter>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="true" />
<Condition Property="IsRequired" Value="true" />
</MultiTrigger.Conditions>
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource IsRequiredDrawing}"/>
</Setter.Value>
</Setter>
</MultiTrigger>
</Style.Triggers>
</Style>

WPF converter for labels' content

I'm trying to override the output of a label, say it contained "Account" and a client wants account rendered as "Member" (so kind of think of this as a localisation converter?)
My Question; is this possible with "hardcoded" content? or MUST i create a static file containing all label content (with iNotifiyStatic of course)? *for binding?
xaml:
<Label Style="{StaticResource LabelLeft}" Content="Account Name:"></Label>
Resource File: Including all attempts made, from multiple sources heres the most meaningful one.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters ="clr-namespace:Company.Resources.Converters">
<converters:LocationLabelsConverter x:Key="LocationLabelsConverter" />
<Style x:Key="LabelLeft" TargetType="{x:Type Label}" >
<Setter Property="Margin" Value="10 0 0 0"></Setter>
<Setter Property="Height" Value="22"></Setter>
<Setter Property="Padding" Value="0 0 0 0"></Setter>
<Setter Property="VerticalContentAlignment" Value="Center"></Setter>
<!-- Att1 -->
<!--<Setter Property="TextBlock.Text" Value="{Binding RelativeSource={RelativeSource self},
Path=Content,
Converter={StaticResource LocationLabelsConverter}}"></Setter>-->
<!-- Att2 -->
<!--<Setter Property="Content">
<Setter.Value>
<Binding Path="Content" RelativeSource="{RelativeSource self}">
<Binding.Converter>
<converters:LocationLabelsConverter/>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>-->
<!-- Att3 -->
<!--<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource self},
Path=Content,
Converter={StaticResource LocationLabelsConverter}}">
<Setter Property="Content" Value="Test123"/>
</DataTrigger>
</Style.Triggers>-->
</Style>
And here's the converter:
[ValueConversion(typeof(string), typeof(string))]
public sealed class LocationLabelsConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value != null)
{
return "hello sweety";// (string)value; //The goal here in the end is to run it through a method to replace string with correct text.
}
else return null;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (string)value;
}
}
you can apply converter like this:
<Label Content="{Binding Source='Account Name:', Converter={StaticResource LocationLabelsConverter}"/>

How to dynamically set style of an element in a User Control

In a WPF project, I have a user control (Valve.xaml) that defines a Polygon shape.
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
</Grid>
I am displaying the Valve user control in a window xaml (FFG.xaml) file, like such:
<Window
<!-- removed other namespaces for brevity -->
xmlns:cl="clr-namespace:FFG.Controls;assembly=PID.Controls">
<Grid>
<cl:Valve x:Name="valve201A"></cl:Valve>
</Grid>
</Window>
I am setting the DataContext of FFG.xaml to class FFG_ViewModel.cs, and it contains an instance of the Valve_Model class. Valve_Model essentially represents the valve that is drawn on the window in FFG.xaml.
public class FFG_ViewModel : ViewModelBase {
public Valve_Model Valve201A { get; set; }
// There are other properties and methods, but I've removed them for brevity also
}
Here is the Valve_Model class:
public class Valve_Model INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private bool _isValveOpen { get; set; }
public bool IsValveOpen {
get {
return _isValveOpen;
}
set {
_isValveOpen = value;
OnPropertyChanged("IsValveOpen");
}
}
#region INotifyPropertyChanged
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null) {
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
QUESTION:
What I want is for the Style property in the Valve.xaml to change when the IsValveOpen property changes.
So if the valve is open then it would be:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
and when the property is changed to false then I need the style of the polygon to be changed to:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Closed}"/>
How do I go about implementing this exactly?
You could use an IMultiValueConverter.
First, let's simplify the use case. Basicly you want to swap Styles based on a given state object, which I'll represent by a ToggleButton. The fact that you're wrapping everything in a UserControl also has no influence on the underlying concept.
Demo:
Starting a fresh project
Declaring our Resources
Feeding the Window and the state to the Converter.
MainWindow.xaml
<Window
...
>
<Window.Resources>
<local:ToStyleConverter x:Key="ToStyleConverter"/>
<Style x:Key="Valve_Open" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style x:Key="Valve_Closed" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="butt" DockPanel.Dock="Bottom">Switch</ToggleButton>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Stretch="Uniform">
<Polygon.Style>
<MultiBinding Converter="{StaticResource ToStyleConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Window}}"/>
<Binding ElementName="butt" Path="IsChecked"/>
</MultiBinding>
</Polygon.Style>
</Polygon>
</DockPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class ToStyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] is Window)
{
Window win = (Window)values[0];
if ((bool)values[1])
return win.FindResource("Valve_Open");
if (!(bool)values[1])
return win.FindResource("Valve_Closed");
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object values, Type[] targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Changing to any specific use case means:
Pointing the relativesource binding to the Control that holds the Resources (Styles)
Using the second binding to add the state to the Converter (DP/INPC)
Implementing Converter logic
You can (should as much as I know) use a DataTrigger, within a ControlTemplate. Assuming that these two are your Styles:
<Window.Resources>
<Style TargetType="Polygon" x:Key="Valve_Open">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style TargetType="Polygon" x:Key="Valve_Close">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
You should add this style to the resources:
<Style x:Key="changeStyle" TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Valve201A.IsValveOpen}" Value="true">
<Setter TargetName="pValve" Property="Style" Value="{StaticResource Valve_Close}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and use them in your views:
<Control DataContext="{Binding}" Style="{StaticResource changeStyle}" />
Instead of setting the actual Style property to a new value, you could add a DataTrigger to the Style itself that changes the properties of the Polygon based on the value of the IsValveOpen source property.
Valve.xaml:
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20">
<Polygon.Style>
<Style TargetType="Polygon">
<!-- Copy the setters from the Valve_Closed style here -->
<Setter Property="Fill" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValveOpen}" Value="True">
<!-- Copy the setters from the Valve_Open style here -->
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Polygon.Style>
</Polygon>
</Grid>
FFG.xaml:
<Grid>
<cl:Valve x:Name="valve201A" DataContext="{Binding Valve201A}" />
</Grid>

stackpanel visibility based on label content not working

I have a stack panel that I want to make visible based on a label's content. Just not sure why it isnt working for me. What's highlighted in bold is what I want to hide. Any suggestion?
<StackPanel Orientation="Horizontal">
<Label Nane="lblCarrier" Content="{Binding Path=Carrier}" />
**<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Content, ElementName=lblCarrier}" Value="">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label x:Name="lblCarrierGrade" Content="Grade Carrier:" />
<TextBox x:Name="txtCarrierGrade1" />
<TextBox x:Name="txtCarrierGrade2" />
</StackPanel>**
It could be that the Content is null rather than String.Empty.
You could try using TargetNullValue
<DataTrigger Binding="{Binding Content, ElementName=lblCarrier,TargetNullValue=''}" Value="">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
Why not using a converter? Add a class file to you project like this:
class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return string.IsNullOrEmpty(value as string) ? Visibility.Hidden : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In your Window definition add this:
xmlns:myNamespace="clr-namespace:[YourProjectName]"
Then somewhere in the resources add this
<myNamespace:VisibilityConverter x:Key="myConverter"/>
Now you can use it:
<Style TargetType="StackPanel">
<Setter Property="Visibility"
Value="{Binding Content, ElementName=lblCarrier,
Converter = {StaticResources myConverter}}"/>

Resources