How to Get The Selected Child Item in a TreeView - c#

I have a TreeView with two levels, parents and children and I would like to get the value of a selected child. I used Josh Smith's TreeView with MVVM pattern to get me started and modified the IsSelected method to get the item that is selected but I'm always getting the parent item.
static object _selectedItem = null;
......
......
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
{
_selectedItem = this;
}
}
}
}
Snippet of my XAML:
Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>

Related

How to bind to another's object property with XAML Style Template?

Let's say I have the following class:
public class MyClass : System.Windows.FrameworkElement
{
public static readonly DependencyProperty HasFocusProperty = DependencyProperty.RegisterAttached("HasFocus", typeof(bool), typeof(MyClass), new PropertyMetadata(default(bool)));
public bool HasFocus
{
get => (bool)GetValue(HasFocusProperty);
set => SetValue(HasFocusProperty, value);
}
public System.Windows.Controls.TextBox TextBox { get; set; }
}
I want to change some UI properties of TextBox via XAML Template Trigger based on the property HasFocus, so I do the following:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:win="clr-namespace:System.Windows.Controls">
<Style TargetType="{x:Type win:TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type win:TextBox}">
<ControlTemplate.Triggers>
<Trigger Property="MyClass.HasFocus" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Red" />
<Setter TargetName="Border" Property="BorderThickness" Value="2" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
However, the style is not applied when setting HasFocus = true.
Within the properties of TextBox, I can see that the trigger is registered. If I change <Trigger Property="MyClass.HasFocus" Value="True"> to <Trigger Property="MyClass.HasFocus" Value="False">, my style is applied initially. So I think my XAML definition is okay.
Any ideas how to solve this?
An element in a template that is applied to a TextBox cannot bind to a property of a MyClass, unless there is a MyClass element to bind to somewhere in the visual tree.
If you want to be able to set a custom HasFocus property of a TextBox, you should create an attached property:
public class FocusExtensions
{
public static readonly DependencyProperty SetHasFocusProperty = DependencyProperty.RegisterAttached(
"HasFocus",
typeof(bool),
typeof(FocusExtensions),
new FrameworkPropertyMetadata(false)
);
public static void SetHasFocus(TextBox element, bool value)
{
element.SetValue(SetHasFocusProperty, value);
}
public static bool GetHasFocus(TextBox element)
{
return (bool)element.GetValue(SetHasFocusProperty);
}
}
It can be set for any TextBox element:
<TextBox local:FocusExtensions.HasFocus="True">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="local:FocusExtensions.HasFocus" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

WPF, setting background color with trigger

I have a customcontrol which render a textbox. I've also a style that set the color of the background based on some conditions as follows:
<Style x:Key="ArtParamStyle" TargetType="av:DC_Base">
<Setter Property="Background" Value="{StaticResource EditableAreaBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Info.Upd.IsAutoCalc}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Forced}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
Initially as the value of my textbox is autocalculated, the background is correctly red. If I also set the Forced as true (by ticking a chebckbox) I've a weird result, the border of textbox is lightgreen but background not.
It seems to be a strange color, a combination of red and lightgreen. As test, if I set the "IsAutoCalc" color as Transparent, the trigger works correctly. How can I solve this?
your code seems to be correct. But I provide you my sample:
XAML:
<Window.Resources>
<Style x:Key="ArtParamStyle" TargetType="TextBox">
<Setter Property="Background" Value="Yellow" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Bool1}" Value="True"/>
<Condition Binding="{Binding Bool2}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Red" />
<Setter Property="BorderBrush" Value="Red" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding Bool2}" Value="True">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="BorderBrush" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Style="{StaticResource ArtParamStyle}" Height="50" Margin="4"/>
<CheckBox IsChecked="{Binding Bool1}"/>
<CheckBox IsChecked="{Binding Bool2}"/>
</StackPanel>
</Grid>
In this case I used Multidatatrigger to set red background when Bool2(your forced) is not checked.
MainWindow.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChaged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private bool bool1;
public bool Bool1
{
get { return bool1; }
set { bool1 = value; RaisePropertyChaged("Bool1"); }
}
private bool bool2;
public bool Bool2
{
get { return bool2; }
set { bool2 = value; RaisePropertyChaged("Bool2"); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
Probably your problem is related to your custom control.

Validate rows in DataGrid

In my application I have a very large DataGrid with several DataGridTemplateColumns. In the resources of the DataGrid there is a style for the DataGridRows. This style looks like:
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter Property="AttachedProperties:DataGridExtensions.FocusOnEditingColumn" Value="{Binding IsEditing}"/>
</DataTrigger>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
In my ViewModel I'm subscribed to the PropertyChanged-Event of the DataObjects which are contained in the ItemsSource of the DataGrid. So if the PropertyChanged-Event is called and the propertyname equals "IsEdit" I'll do some validations for all rows in the DataGrid. Therfor I'm using the following method:
private void CheckConsistence()
{
foreach (Module module in SelectedGroup.Modules)
{
string currentTarget = ResolveTargetPath(module);
foreach (Module toCompare in SelectedGroup.Modules.Except(new[] { module }))
{
string toCompareTarget = ResolveTargetPath(toCompare);
if (string.Compare(currentTarget, toCompareTarget, StringComparison.InvariantCultureIgnoreCase) == 0)
{
module.IsInvalid = true;
ValidationAdvices.Add(new ValidationAdvice("Duplicated path"));
}
}
}
}
If the IsInvalid-Property in the Module-Object is set to true I change the Error-Object. A part of the Module-Object looks like:
public class Module : INotifyPropertyChanged, IDataErrorInfo
{
private bool isEditing;
public bool IsEditing
{
get { return isEditing; }
set
{
isEditing = value;
OnPropertyChanged(() => IsEditing);
}
}
private bool isInvalid;
public bool IsInvalid
{
get { return isInvalid; }
set
{
isInvalid = value;
if (isInvalid)
{
error = "Error";
}
else
{
error = null;
}
OnPropertyChanged(() => IsInvalid);
OnPropertyChanged(() => Error);
}
}
public string this[string columnName]
{
get { return string.Empty; }
}
private string error;
public string Error
{
get { return error; }
}
}
My DataGrid has a RowValidationErrorTemplate which looks like:
<ControlTemplate x:Key="RowErrorTemplate" TargetType="Control" x:Shared="False">
<Grid ClipToBounds="False" Panel.ZIndex="10000">
<AdornedElementPlaceholder Name="adornedElement"/>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Width="16" Height="16"
Source="pack://application:,,,/MyApp.UI.Resources;component/Graphics/Error_16x16.png"
ToolTipService.IsEnabled="True"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding Converter={converters:ModuleValidationErrorConverter}}"/>
</Grid>
</ControlTemplate>
My problem is now if two rows have the same path both rows should be displayed as error-rows. But only one row will get the errortemplate and the red-background defined in the Style for the DataGridRow. The call of ValidationAdvices.Add(new ValidationAdvice("Duplicated path")); is done for each row. What do I have to do so that every row gets the validationerrortemplate?
I also have set the DataGrid.RowValidationRules like:
<DataGrid.RowValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="True" ValidationStep="UpdatedValue"/>
</DataGrid.RowValidationRules>

DataTrigger triggered by boolean

I want to change the foreground setter property using a boolean equals true.
I tried:
public bool RED = false;
if (condition)
{
RED = true;
}
and have the DataTrigger triggered by the boolean:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="16" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RED}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
But no luck. Whats the best/simplest way to write this?
You need to create a property. What you have is a field. Also, you need to implement INotifyPropertyChanged or use a DependencyProperty or other type type of property that is able to notify a WPF binding.
private bool _red;
public bool Red {
get { return _red; }
set
{
_red = value;
OnPropertychanged();
}
}

WPF DataGrid CurrentItem is null

I have a WPF toolkit DataGrid that is bound to an observable collection of objects in the view model. In this DataGrid, I have defined a DataGridTemplateColumn for a certain field of that object. (Car.Name)
I'm trying to detect duplicates and set a certain style on the cell that already exists in another list of (similar) objects.
When this dialog gets loaded there is no selection. The IsDuplicate in the view model does get called for each item of the row, but I am unable to tell which item it's currently on in the view model. I thought of using CurrentItem, but it seems to always be null.
Question: How do I know in the View Model which current item is being called?
View XAML:
<toolkit:DataGrid ItemsSource="{Binding Cars}"
CurrentItem="{Binding CurrentCar}">
...
<toolkit:DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type toolkit:DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType=toolkit:DataGrid},
Path=DataContext.IsDuplicate}" Value="False">
<Setter Property="BorderBrush" Value="Transparent" />
</DataTrigger>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType=toolkit:DataGrid},
Path=DataContext.IsDuplicate}" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="ToolTip" Value="Duplicate" />
</DataTrigger>
</Style.Triggers>
</Style>
</toolkit:DataGridTemplateColumn.CellStyle>
ViewModel.cs:
public Car CurrentCar { get; set; }
public bool IsDuplicate
{
get
{
// Logic to check current car against a list of cars
var x = CurrentCar; // null
}
}
| Name | ...
| Car 1 | ... <-- Highlight
| Car 2 | ...
| Car 1 | ... <-- Highlight
You're thinking about it the wrong way. This shouldn't be an iterative method. IsDuplicate needs to be a property of Car, with a link to the collection so that each Car object determines if there are other items in the collection that match it.
public class Car
{
public Guid Id { get; set; }
public Collection<Car> Cars { get; set; }
public bool IsDuplicate
{
get
{
// Logic to check current car against a list of cars
return (Cars.Count(c => c.Id.Equals(this.Id))) > 1;
}
}
}
Then in XAML:
<toolkit:DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type toolkit:DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="IsDuplicate" Value="False">
<Setter Property="BorderBrush" Value="Transparent" />
</DataTrigger>
<DataTrigger Binding="IsDuplicate" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="ToolTip" Value="Duplicate" />
</DataTrigger>
</Style.Triggers>
</Style>
</toolkit:DataGridTemplateColumn.CellStyle>
Not so sure about the XAML binding syntax, that's just off the top of my head. But you get the idea.
Try:
<toolkit:DataGrid ItemsSource="{Binding Cars}"
SelectedItem="{Binding CurrentCar}">

Categories