WPF DataTrigger never fires to Expand TreeView - c#

I have the following XAML which should expand my whole treeview when a specific property ExpandNodes is True, but it is never triggered.
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding ExpandNodes}" Value="True">
<Setter Property="IsExpanded" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding ExpandNodes}" Value="False">
<Setter Property="IsExpanded" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
// some other code
</TreeView>
In my ViewModel I set ExpandNodes to True during a specific event, yet the treeview remains non expanded. It's not an issue with my DataContext as I have other properties from the same Viewmodel that are bound and work fine.
My Viewmodel:
private bool _expandnodes;
public bool ExpandNodes
{
get
{
return _expandnodes;
}
set
{
_expandnodes = value;
OnPropertyChanged("ExpandNodes");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}

Too compliacted, you can simply bind IsExpanded property to your object and raise PropertyChanged event.
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding ExpandNodes,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
TargetNullValue=False}" />
</Style>
</TreeView.ItemContainerStyle>
// some other code
</TreeView>

Related

Image Visibility DataTrigger Set Default to False

I have an image that need to be showed based on condition, is that attachment file or not. The problem is, I've set trigger that set the value of the condition, but seems like the condition isn't work and the value always set to true.
<Image
Width="30"
Height="30"
Source="Resources/Images/chat_file_attach.png">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding AttachStat}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding AttachStat}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
The question is. Is there any way to make the default value to false? I've set it to true on the C# looping data, whenever the condition is attachment included.
Take a look in your output window and search for:
System.Windows.Data Warning: 40 : BindingExpression path error
I think the AttachStat property is not available in the image's DataContext.
Use a single DataTrigger and make sure that the DataContext of the Image has a public AttachStat property:
<Image x:Name="img" Width="30" Height="30" Source="Resources/Images/chat_file_attach.png">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding AttachStat}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
img.DataContext = new YourClass();
...
public class YourClass : INotifyPropertyChanged
{
private bool _attachStat;
public bool AttachStat
{
get
{
return _attachStat;
}
set
{
_attachStat = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You can use a BoolToVisibilityConverter to convert a Boolean to and from a Visibility value.
In the resource section:
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
And the image:
<Image Width="30"
Height="30"
Source="Resources/Images/chat_file_attach.png"
Visibility="{Binding AttachStat,
Converter={StaticResource BoolToVisibilityConverter}}" />

Creating a self-updating Textblock user control in WPF

I'm trying to create a re-usable textblock user control in WPF. The basic idea is as follows:
User does not directly specify the content of the textblock
There are three dependancy properties in my user control called IsToggled, ToggleTrueText, and ToggleFalseText.
The control will display ToggleTrueText if IsToggled is true; or display ToggleFalseText if IsToggled is false.
When IsToggled changes during runtime, the text automatically changes to either ToggleTrueText or ToggleFalseText
I started by adding a PropertyChangedCallback to the IsToggled DP:
Code-behind of the UserControl:
public static readonly DependencyProperty IsToggledProperty =
DependencyProperty.Register("IsToggled", typeof(bool),
typeof(TagToggle), new PropertyMetadata(new
PropertyChangedCallback(OnToggleStateChanged)));
public bool IsToggled
{
get { return (bool)GetValue(IsToggledProperty); }
set { SetValue(IsToggledProperty, value); }
}
//ToggleTrueText and ToggleFalseText are declared similarly to IsToggled
...
private static void OnToggleStateChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
...
}
Xaml of the user control:
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="TheTextBlock" Text="{Binding WhatDoIBindTo}"/>
</Grid>
However, I'm not sure what would be the best way to ensure that TheTextBlock updates its text whenever IsToggled changes during runtime.
Try this:
private static void OnToggleStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TagToggle ctrl = d as TagToggle;
if (ctrl != null)
{
TheTextBlock.Text = ctrl.IsToggled ? ToggleTrueText. : ToggleFalseText;
}
}
If you want to bind the Text property of the TextBlock you need to make sure that you are binding to properties of the UserControl. You could do this by setting the DataContext property of the TextBlock:
<TextBlock x:Name="TheTextBlock" DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding ToggleTrueText}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsToggled}" Value="False">
<Setter Property="Text" Value="{Binding ToggleFalseText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
You can used trigger for this
Please check below code
<TextBlock x:Name="TheTextBlock">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsToggled}" Value="True">
<Setter Property="Text" Value="{Binding ToggleTrueText}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsToggled}" Value="False">
<Setter Property="Text" Value="{Binding ToggleFalseText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

WPF ListBox data validation

I have a ListBox of string items where I want to validate the strings every time a string is added or removed.
Below is the code I've cobbled together, but the problem is that ValidateAddresses is never called when the ObservableCollection Addresses changes.
Intended behavior is that when an invalid string is found, a red border should be shown around the ListBox with a tooltip that displays the error message.
This INotifyDataErrorInfo setup works fine for TextBoxes, so I dunno what I am doing wrong here.
ViewModel
[CustomValidation(typeof(ItemViewModel), "ValidateAddresses")]
public ObservableCollection<string> Addresses
{
get
{
return item.Addresses;
}
set
{
item.Addresses = value;
NotifyPropertyChanged(nameof(Addresses));
}
}
XAML
<Grid>
<Grid.Resources>
<Style TargetType="ListBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="TextErrorTemplate">
<DockPanel LastChildFill="True">
<AdornedElementPlaceholder>
<Border BorderBrush="Red" BorderThickness="2"/>
</AdornedElementPlaceholder>
<TextBlock Foreground="Red"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ListBox ItemsSource="{Binding Path=Item.Addresses, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" SelectedIndex="{Binding Path=SelectedAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Validation method (never called)
public static ValidationResult ValidateAddresses(object obj, ValidationContext context)
{
ItemViewModel item = (ItemViewModel)context.ObjectInstance;
if (item.Addresses.Count > 0)
{
foreach (string address in item.Addresses)
{
if (Regex.IsMatch(address, #"[^\w]"))
return new ValidationResult($"{address} is not a valid address.", new List<string> { "Addresses" });
}
}
return ValidationResult.Success;
}
I ended up adding following in class constructor for each ObservableCollection I had.
Addresses.CollectionChanged += (sender, eventArgs) => { NotifyPropertyChanged(nameof(Addresses)); };
I tried to look into circumstances upon which unsubscribing from events is required to prevent memory leaks, but it does not seem like this is one of these cases so cleanup shouldn't be required.
Null check is not required because ObservableCollections are all initialized in Model class constructor.
Thank you for your replies.
And this is the code for NotifyPropertyChanged.
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Highlight one item in DataGrid

I'm displaying Cars in a DataGrid and would like to highlight one special car, the CurrentlySelectedCar.
When the user double clicks on a car, this car is saved as CurrentlySelectedCar in my MainViewModel. Now, whenever the user comes back to the DataGrid, I would like to highlight this car = row, e.g. by using a red background.
I have found out how to highlight rows in a DataGrid based on certain values, but in my case, all I have is the CurrentlySelectedCar.
My First try:
<Style TargetType="DataGridRow">
<Style.Triggers>
<!-- not working-->
<DataTrigger Binding="{Binding CurrentlySelectedCar}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
My second try:
<Style TargetType="DataGridRow">
<Style.Triggers>
<!-- not working either, "Binding can only be set on DependencyProperty of DependecyObject"-->
<DataTrigger Binding="{Binding Guid}" Value="{Binding CurrentlySelectedCar.Guid}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
How can I highlight the row with this information?
I think that you have to do something like this described in this answer: Using bindings in DataTrigger condition
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource someMultiConverter}">
<Binding Path="Guid"></Binding>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Datagrid}}" Path="CurrentlySelectedCar.Guid"></Binding>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
You have to write a multiconverter that return true if the two Guid are equals.
Try the following instead, since each datagrid row has an IsSelected property, you can bind to it directly.
<DataGrid EnableRowVirtualization="False">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" >
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
Updated response
The problem right now is your DataContext is the DataGridRow, and it doesn't have access to the MainViewModel. You can address this by passing the DataGridRow to a converter that is aware of the current MainViewModel. However, it is a lot of code, and is barely comprehensible.
<Window>
<Window.Resources>
<local:IsCurrentlySelectedCarConverter x:Key="IsCurrentlySelectedCarConverter" />
</Window.Resources>
...
<DataTrigger Binding="{Binding
Path=DataContext,
Converter={StaticResource IsCurrentlySelectedCarConverter}}" >
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Window>
Here's corresponding converter
public class IsCurrentlySelectedConverter : IValueConverter
{
public MainViewModel MainViewModel { get; set;}
public object Convert(object value, ....)
{
return (value == MainViewModel.CurrentlySelectedCar);
}
}
and you'll need to wire in the converter's MainViewModel manually in your view
this.Resources["IsCurrentlySelectedCarConverter"].MainViewModel = _mainViewModel;
and at this point you'd have to question the maintainability of the monster that has been created.
It may be better to replace each Car with a CarViewModel so that it has a property called IsSelected and you can maintain that in code. The following in my opinion is easier to follow what is actually going on.
public class CarViewModel : INotifyPropertyChanged
{
public MainViewModel { get; set; }
public bool IsSelected { get { return this == MainViewModel.CurrentlySelectedCar; } }
// Call RaisePropertyChanged("IsSelected") whenever
// CurrentlySelectedCar is changed
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}

WPF window doesn't get updated by property binding

I have a WPF form with a textbox which is defined in xaml file as follows:
<TextBox Grid.Column="1" Grid.Row="9" TabIndex="0" x:Name="txtboxExample" Width="170" >
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding ToolDataContext.ItemInstance.IsToShow, Mode=TwoWay}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<TextBox.Text>
<Binding Path="ToolDataContext.ItemInstance.UserText" UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>
...
<Button Click="someBtn_Click" Content="{x:Static res:Strings.ButtonString}" Name="someButton">
on the xaml.cs file I have the following code:
private void someBtn_Click(object sender, RoutedEventArgs e)
{
...
ToolDataContext.ItemInstance.IsToShow = true;
...
}
In the Item class I have the following code for the property IsToShow:
public class Item : SyncableObject, ISearchableObject, INotifyPropertyChanged
{
...
private bool _isToShow;
public bool IsToShow
{
get { return _isToShow; }
set
{
if (value == _isToShow)
return;
_isToShow = value;
this.OnPropertyChanged("IsToShow");
}
}
...
new public event PropertyChangedEventHandler PropertyChanged;
new public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
...
}
I would expect the window to show the textbox when I click when I on the button.
But it doesn't happen.
Can anyone give me a lead about what am I doing wrong?
Try adding Path to your data trigger binding
<DataTrigger Binding="{Binding Path=ToolDataContext.ItemInstance.IsToShow, Mode=TwoWay}" Value="True">
<DataTrigger Binding="{Binding ToolDataContext.ItemInstance.IsToShow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
Assuming that you have assigned the datacontext, you need to add UpdateSourceTrigger=PropertyChanged

Categories

Resources