I have set up a TreeView with HierarchicalDataTemplate.
I want to set the IsSelected property of a TreeViewItem by Binding so in my ViewModel I have a Property
"ObjectToSelectInTreeView" which holds the object.
In the TreeView I have a Style with multibinding
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected">
<Setter.Value>
<MultiBinding Converter="{StaticResource IsSelectedConverter}" Mode="OneWay">
<Binding ElementName="TreeViewControl" Path="DataContext.DocumentToSelectInTree"
UpdateSourceTrigger="PropertyChanged"></Binding>
<Binding Path="."></Binding>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsDeleted}" Value="True">
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
And the Converter:
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ModelBase itemForComparison;
if (values[0] != null)
{
if (values[1] is ObservableDocument)
itemForComparison = ((ObservableDocument)values[1]).Document;
else
itemForComparison = (ModelBase)values[1];
if (values[0] == itemForComparison)
return true;
}
return false;
}
First time all works.
But when I manually select all items in TreeView and then set the ObjectToSelectInTreeView the converter will never run again and so no object is selected.
Thanks for every help!
DataContext.DocumentToSelectInTree property changed is going to trigger the calculation of IsSelectedConverter converter.
I hope you might have implemented INotifyPropertyChanged interface for your ViewModel.
Write the ItemSelectedCommand for the TreeView if you have not written already, and trigger the PropertyChanged Notification for DocumentToSelectInTree property whenever the ItemSelected is executed
Related
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>
I am trying to bind visibility of a styled control to the condition that its tag is equal to the selected index of a TabControl.
I use RelativeSource TemplatedParent but it's not being set, and I suspect it's because I am not setting Template property in Style.
Here is my code:
<Grid x:Name="Telas" Grid.Column="1">
<Grid.Resources>
<vw:TagIsIndexBooleanConverter x:Key="TagIsIndexBooleanConverter"/>
<Style x:Key="SelectedIndexVisibleStyle" TargetType="UserControl">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TagIsIndexBooleanConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="SelectedIndex" ElementName="menu"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<vw:TelaColetaView
x:Name="telaColeta"
DataContext="{Binding TelaColetaVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="0"/>
<vw:TelaPacientesView
x:Name="telaPacientes"
DataContext="{Binding TelaPacientesVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="1"/>
<vw:TelaConfiguraçõesView
x:Name="telaConfigurações"
DataContext="{Binding TelaConfiguraçõesVM}"
Style="{StaticResource SelectedIndexVisibleStyle}"
Tag="4"/>
</Grid>
And converter:
public class TagIsIndexBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(v => v == null || v == DependencyProperty.UnsetValue))
return Binding.DoNothing;
var tag = System.Convert.ToInt32(values[0]);
var index = System.Convert.ToInt32(values[1]);
var result = tag == index;
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can't use TemplatedParent in this case because there isn't one; that's meant to used inside a ControlTemplate to specify that the source for the binding is the control that you're applying the template to.
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}"/>
But this isn't inside a ControlTemplate. Instead, Tag is just a property of the thing you're styling. Ordinarily you'd do a trigger like this for Tag:
<Trigger Property="Tag" Value="0">
...but you need a multibinding to get the SelectedIndex from menu, and it's got to be a MultiDataBinding because you need to specify ElementName.
So you need a binding. To bind to one of your own properties instead of a property of your DataContext, you bind with a RelativeSource of Self:
<Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
OP also found he had to set the TargetType of the Style to "UserControl", for the Tag binding to work.
We have a control that may or may not be hosted in a popup control. In the case when it is, we want to set properties on the popup using RelativeSource and OneWayToSource bindings. In cases where it's not hosted in a popup, we want the binding to basically be ignored/do nothing.
Only thing I can think of is binding to self with a custom converter which internally walks the visual tree looking for the popup. If found, do the magic. If not, do nothing. But I'm wondering if it can be done purely with XAML binding syntax.
In cases where it's not hosted in a popup, we want the binding to basically be ignored/do nothing.
Since you have a control one can create a Boolean dependency property, a flag, which can trigger either one of two hidden controls which behaves in a specific way due to which way the boolean is set.
I would call this the standard way, for the control is not required to know anything about the consumer, the consumer specifies the state.
Or
to set properties on the popup using RelativeSource and OneWayToSource bindings.
Similar to above, with two distinct hidden controls but then have the a style look for a specific window and a specific property. Then either hide or show the controls depending on what is found:
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsPopup,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}}"
Value="True">
<Setter Property="IsEnabled"
Value="True" />
</DataTrigger>
</Style.Triggers>
Try following code:
<Border>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="Exists"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}}" Value="{x:Null}">
<Setter Property="Text" Value="No stackpanel"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
It will display both "No stackpanel" and error in Output window. If you place StackPanel inside Border "Exists" will be displayed. Set anything you wish inside DataTrigger when condition is fulfilled.
In case you want to avoid receiving error:
<Window.Resources>
<local:IsParentTypePresentToBoolConverter x:Key="IsParentTypePresentToBoolConverter"/>
</Window.Resources>
<Border>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="No StackPanel"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource IsParentTypePresentToBoolConverter}}" Value="True">
<Setter Property="Text" Value="Stackpanel exists as parent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
converter which detects whether such a type is present as a parent:
class IsParentTypePresentToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var isPresent = FindParent<StackPanel>((DependencyObject) value);
return isPresent != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
private T FindParent<T>(DependencyObject child) where T : DependencyObject
{
var parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
var parent = parentObject as T;
return parent?? FindParent<T>(parentObject);
}
}
And here you have more generic equivalent where you make a use of reflection in order to find parent type.
class IsParentTypePresentToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var method = GetType().GetMethod("FindParent").MakeGenericMethod(new Type[1] { (Type)parameter });
var foundObject = method.Invoke(this, new object[] { (DependencyObject)value });
return foundObject != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public T FindParent<T>(DependencyObject child) where T : DependencyObject
{
var parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
var parent = parentObject as T;
return parent ?? FindParent<T>(parentObject);
}
}
The only distinction in XAML is that you indicate searching type of object.
<Border>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="No StackPanel"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource IsParentTypePresentToBoolConverter},
ConverterParameter={x:Type Border}}" Value="True">
<Setter Property="Text" Value="Stackpanel exists as parent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
I have an editor view that can be used for multiple edited objects. The view model for multiple objects provides a property like Field1Multiple of type bool for each field that needs to be handled. In this case, it's only ComboBox controls for now. Whenever multiple differing values shall be indicated for that field, a certain style should be applied to that control which is defined in App.xaml. That style changes the background of the control to visualise that there is no single value that can be displayed here.
I've tried with this XAML code:
<ComboBox
ItemsSource="{Binding Project.Field1Values}" DisplayMemberPath="DisplayName"
SelectedItem="{Binding Field1}">
<ComboBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Field1Multiple}" Value="true">
<Setter
Property="ComboBox.Style"
Value="{StaticResource MultiValueCombo}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
But it doesn't work because I cannot set the Style property from inside a Style. If I use triggers directly on the control, there may only be EventTriggers, no DataTriggers, the compiler says.
How can I set the control's style based on a binding value? Or, how can I set a certain style for a control if a binding value is true?
(EDIT to full solution)
You can use converter:
public class AnyIsMultipleToStyle : IValueConverter
{
public Style NormalStyle { get; set; }
public Style MultiStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
IList<SampleClass> list= value as IList<SampleClass>;
if (list!=null)
{
if (list.Any(i => i.Multi))
return MultiStyle;
}
}
return NormalStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in your xaml:(You indicate normal style and multistyle to converter)
<Window.Resources>
<Style x:Key="MultiValueCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Olive" />
</Style>
<Style x:Key="NormalCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Red" />
</Style>
<my:AnyIsMultipleToStyle x:Key="AnyIsMultipleToStyle1" MultiStyle="{StaticResource MultiValueCombo}" NormalStyle="{StaticResource NormalCombo }" />
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Items, ElementName=root}" >
<ComboBox.Style>
<Binding Converter="{StaticResource AnyIsMultipleToStyle1}" Path="Items" ElementName="root" >
</Binding>
</ComboBox.Style>
</ComboBox>
</Grid>
I have a bool property in my ViewModel called IsConnected and I would like to bind it to a TextBlock in my MainWindow. Rather than have the textblock read true or false I need it to say Connected or Disconnected instead. Forgive me because I'm new to WPF. If someone could give me a head start I can take it from there but I'm not sure how to figure out what I need.
Easiest way is probably to create a custom converter which converts your bool value to a string. Search anywhere for IValueConverter and/or WPF.
public class BoolToConnectedConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if((bool)value)
return "Connected";
else
return "Disconnected";
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
add xmlns:
xmlns:converter="clr-namespace:MyProjectNameSpace"
add resource to XAML (change to whatever element needed)
<Window.Resources>
<converter:BoolToConnectedConverter x:Key="connectedConverter" />
</Window.Resources>
in XAML:
<TextBlock Text={Binding IsConnected, Converter={StaticResource connectedConverter}" />
I'd generally prefer to just add a property to the view model (I really dislike value converters), but here's a simple way to accomplish what you're trying to do using a style:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="Connected"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Text" Value="Disconnected"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Edit
Note that once you get used to using data triggers, you can make all kinds of modifications to your view without touching your view model. For instance:
<StackPanel>
<Image Source="images\connected.png">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Image Source="images\disconnected.png">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
Using ViewModel, you write two property wrap, and notify changes in the real property.
So that whenever the value is changed, you the string representation will update and bind to controls, while you can still use the bool property in the code.
public string IsConnectedStr{
get{
return IsConnected?"Connected":"Disconnected";
}
}
public bool IsConnected{
get{
return _isConnected;
}
set{
_isConnected=value;
PropertyChanged("IsConnected");
PropertyChanged("IsConnectedStr");
}
}
You could do this in two ways
1) Write a converter
2) Change the function in the ViewModel so that it returns the desired string instead of a bool
The easiest way is #2, but if you really need the bool value somewhere else in your code you go with #1 (google converter and wpf)
Take a look at value converters.
http://www.wpftutorial.net/ValueConverters.html
public class BoolToConnectedConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var isConnected = (bool)value;
return isConnected ? "Connected" : "Disconnected";
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException("Not required for read-only values");
}
}
In your XAML:
<Window.Resources>
<l:BoolToConnectedConverter x:Key="boolToConnectedConverter" />
</Window.Resources>
<Grid>
<Label Content="{Binding IsConnected, Converter={StaticResource boolToConnectedConverter}}" />
</Grid>