Wpf bind property in ItemContainerStyle to TextBlock in DataTemplate of ItemTemplate - c#

I want to bind AutomationProperties.Name to text that containd in secondTextBox how to do that? Using only xaml without code behind.
Xaml:
<Window x:Class="WpfApplication5.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:WpfApplication5"
xmlns:converters="clr-namespace:WpfApplication5.Converters"
mc:Ignorable="d"
Title="TestWindow" Height="350" Width="525">
<Window.Resources>
<converters:StrangeConverter x:Key="CommonConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ComboBox Height="20" Width="100" ItemsSource="{Binding comboBoxItems}" AutomationProperties.AutomationId="ID_COMBO1">
<ComboBox.ItemContainerStyle>
<Style>
<!--<Setter Property="AutomationProperties.Name" Value="{Binding UniqNumber}"/>-->
<Setter Property="AutomationProperties.Name" Value="{Binding Path=Text, ElementName=secondTextBox}"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="secondTextBox">
<Run Text="{Binding Name}"></Run>
<Run Text="-"></Run>
<Run Text="{Binding UniqNumber}"></Run>
<Run Text="{Binding .,Converter={StaticResource CommonConverter}}"></Run>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<ComboBox Height="20" Width="100" ItemsSource="{Binding comboBoxItems}" AutomationProperties.AutomationId="ID_COMBO2"/>
<Button Grid.Row="1" Height="20" Width="100" Click="Button_Click"/>
</Grid>
</Window>
Converter:
namespace WpfApplication5.Converters
{
class StrangeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return "Test";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Code:
namespace WpfApplication5
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ViewModel Vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = Vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var _calculatorAutomationElement = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "TestWindow"));
var combobox = _calculatorAutomationElement.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.AutomationIdProperty, "ID_COMBO1"));
Vm.SelectComboboxItem(combobox, "02 - Basic Get");
combobox = _calculatorAutomationElement.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.AutomationIdProperty, "ID_COMBO2"));
Vm.SelectComboboxItem(combobox, "01 - Basic Set");
}
}
public class ViewModel
{
public List<Item> comboBoxItems { get; set; }
public ViewModel()
{
comboBoxItems = new List<Item>();
comboBoxItems.Add(new Item { Name = "Basic Get", UniqNumber = 1 });
comboBoxItems.Add(new Item { Name = "Basic Set", UniqNumber = 2 });
comboBoxItems.Add(new Item { Name = "Basic Report", UniqNumber = 3 });
}
public bool SelectComboboxItem(AutomationElement comboBox, string item)
{
(comboBox.GetCurrentPattern(ExpandCollapsePattern.Pattern) as ExpandCollapsePattern).Expand();
PropertyCondition findCondition = new PropertyCondition(AutomationElement.NameProperty, item);
var comboBoxItems = comboBox.FindFirst(TreeScope.Children, findCondition);
if (comboBoxItems != null)
{
var selectionItemPattern = comboBoxItems.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
selectionItemPattern.Select();
return true;
}
return false;
}
}
public class Item
{
public string Name { get; set; }
public int UniqNumber { get; set; }
}
}

Update the Item class as:
public class Item
{
private string _TextToDisplay;
public string Name { get; set; }
public int UniqNumber { get; set; }
public string TextToDisplay
{
get
{
_TextToDisplay = Name + "-" + UniqNumber; //Add other modification from converter
return _TextToDisplay;
}
set
{
_TextToDisplay = value;
}
}
}
Bind the AutomationProperties.Name to this property
<TextBlock x:Name="secondTextBox" Text="{Binding TextToDisplay}" AutomationProperties.Name="{Binding TextToDisplay}">

This should work without additional view model property and without converter:
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="AutomationProperties.Name">
<Setter.Value>
<MultiBinding StringFormat="{}{0} - {1} Test">
<Binding Path="Name"/>
<Binding Path="UniqNumber"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1} Test">
<Binding Path="Name"/>
<Binding Path="UniqNumber"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>

Related

DataContext binding in UserControl WPF

Trying to get DataContext in UserControl.
My
structure
I have the model Car
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace AutoShop.MVVM.Model
{
class Car : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
private string _Model;
public string Model
{
get
{
return _Model;
}
set
{
_Model = value;
OnPropertyChanged();
}
}
private string _Mark;
public string Mark
{
get
{
return _Mark;
}
set
{
_Mark = value;
OnPropertyChanged();
}
}
private float _Volume;
public float Volume
{
get
{
return _Volume;
}
set
{
_Volume = value;
OnPropertyChanged();
}
}
private int _DateOfIssue;
public int DateOfIssue
{
get
{
return _DateOfIssue;
}
set
{
_DateOfIssue = value;
OnPropertyChanged();
}
}
public enum EngineTypes {
DISEL,
PETROL
};
private EngineTypes _EngineType;
public EngineTypes EngineType
{
get
{
return _EngineType;
}
set
{
_EngineType = value;
OnPropertyChanged();
}
}
private string _ImageUrl;
public string ImageUrl
{
get
{
return _ImageUrl;
}
set
{
_ImageUrl = value;
OnPropertyChanged();
}
}
public Car()
{
}
}
}
And I have main view model
using AutoShop.MVVM.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace AutoShop.MVVM.ViewModel
{
class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
public HomeViewModel HomeVM;
private object _CurrentPage;
public object CurrentPage
{
get
{
return _CurrentPage;
}
set
{
_CurrentPage = value;
OnPropertyChanged();
}
}
private List<Car> _Cars;
public List<Car> Cars
{
get
{
return _Cars;
}
set
{
_Cars = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
Cars = new List<Car>() {
new Car
{
Mark = "Audi",
Model = "asdf",
Volume = 1.4F,
DateOfIssue = 2019,
EngineType = Car.EngineTypes.DISEL,
ImageUrl = "img/img"
},
new Car
{
Mark = "Moto",
Model = "asdf",
Volume = 1.4F,
DateOfIssue = 2019,
EngineType = Car.EngineTypes.DISEL,
ImageUrl = "img/img"
},
new Car
{
Mark = "Some",
Model = "asdf",
Volume = 1.4F,
DateOfIssue = 2019,
EngineType = Car.EngineTypes.DISEL,
ImageUrl = "img/img"
}
};
HomeVM = new HomeViewModel();
CurrentPage = HomeVM;
}
}
}
I need to display cars on the ProductPage.xaml and I do it by the next way
<UserControl x:Class="AutoShop.MVVM.View.ProductPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AutoShop.MVVM.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
>
<StackPanel Background="#fff">
<WrapPanel>
<Grid Width="200px" Margin="30 0 0 0">
<TextBox x:Name="field4" Tag="C:\Users\user\Desktop\Learning\Весенний семестр\ООТП\AutoShop\AutoShop\Images\u.png" Style="{StaticResource TextBoxTemplate}" />
<TextBlock IsHitTestVisible="False" Text="Марка" Padding="20 10 0 0">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="FontFamily" Value="/Fonts/#Montserrat Light" />
<Setter Property="Foreground" Value="#ccc" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=field4}" Value="">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Grid Width="200px" Margin="30 0 0 0">
<TextBox x:Name="field7" Tag="C:\Users\user\Desktop\Learning\Весенний семестр\ООТП\AutoShop\AutoShop\Images\u.png" Style="{StaticResource TextBoxTemplate}" />
<TextBlock IsHitTestVisible="False" Text="username" Padding="20 10 0 0">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="FontFamily" Value="/Fonts/#Montserrat Light" />
<Setter Property="Foreground" Value="#ccc" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=field7}" Value="">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Grid Width="200px" Margin="30 0 0 0">
<ComboBox Style="{StaticResource ComboBoxTheme}" SelectedIndex="0">
<ComboBoxItem>
<TextBlock Text="asdasdasd" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="fsdfsd" />
</ComboBoxItem>
</ComboBox>
</Grid>
</WrapPanel>
<Grid Height="400">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="ItemsWrapper" ItemsSource="{Binding Cars}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10" BorderBrush="Black" BorderThickness="2" Height="279">
<Grid Height="279" Width="200">
<Image Source="{Binding Path=Image}" Width="100" Height="100" VerticalAlignment="Top" Margin="0 10 0 0" />
<TextBlock Text="{Binding Path=Name, StringFormat='Name: {0}'}" VerticalAlignment="Top" Margin="0,120,0,0"/>
<TextBlock Text="{Binding Path=Mark, StringFormat='Rating: {0}' }" VerticalAlignment="Top" Margin="0 180 0 0" />
<TextBlock Text="{Binding Path=Model, StringFormat='Category: {0}'}" VerticalAlignment="Top" Margin="0,200,0,0" />
<TextBlock Text="{Binding Path=Volume, StringFormat='Price: {0}'}" VerticalAlignment="Top" Margin="0,160,0,0" />
<TextBlock Text="{Binding Path=DateOfIssue, StringFormat='Description: {0}'}" VerticalAlignment="Top" Margin="0,140,0,0" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</StackPanel>
</UserControl>
And it the MainForm.xaml I added ProductPage.xaml
<ContentControl Margin="10" Grid.Column="1" Content="{Binding CurrentPage}"/>
The problem is that nothing is being outputted, I think it might be due to the loss of context. How do I properly pass the context to UserControl (ProductPage.xaml)?
ProductPage.xaml
Update:
I set DataContext for MainWindow
And DataContext working becouse {Binding CurrentPage} is working, but binding on Cars field is not working
Add this to the Window.Resources. Of course DataContext must be set for the MainForm.
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type xmlnsForVewModel:MainViewModel}">
<ProductPage/>
</DataTemplate>
</Window.Resources>
</Window>
and if for ProductPage HomeViewModel supposed to be a DataContext, then HomeViewModel has to have Cars property, not MainViewModel.
1.find the MainForm.xaml.cs
2.find ctor
public MainForm()
{
InitializeComponent();
//set the MainForm DataContext
DataContext = new MainViewModel();
}
3.you should add ContentControl.DataTemplate to show your HomeViewModel
----20210427 update
Here demo i make, project name is WpfApp4
MainWindow.xaml
<Window x:Class="WpfApp4.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:WpfApp4"
xmlns:views="clr-namespace:WpfApp4.Views"
xmlns:viewModels="clr-namespace:WpfApp4.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<viewModels:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding ItemsSource, Mode=OneWay}" SelectedItem="{Binding SelectedCarViewModel, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<ContentControl Content="{Binding SelectedCarViewModel}">
<ContentControl.ContentTemplate>
<!--Style your product page here-->
<DataTemplate DataType="{x:Type viewModels:CarViewModel}">
<UniformGrid Columns="2">
<TextBlock Text="Name"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Volume"/>
<TextBlock Text="{Binding Volume}"/>
<TextBlock Text="Price"/>
<TextBlock Text="{Binding Price}"/>
</UniformGrid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
MainWindowViewModel.cs
using System.Collections.ObjectModel;
namespace WpfApp4.ViewModels
{
class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<CarViewModel> ItemsSource { get; } = new ObservableCollection<CarViewModel>();
private CarViewModel selectedCarViewModel;
public CarViewModel SelectedCarViewModel
{
get { return this.selectedCarViewModel; }
set { SetProperty(ref this.selectedCarViewModel, value); }
}
public MainWindowViewModel()
{
ItemsSource.Add(new CarViewModel() { Name = "BMW", Volume = 10, Price = 100 });
ItemsSource.Add(new CarViewModel() { Name = "Toyota", Volume = 5, Price = 80 });
ItemsSource.Add(new CarViewModel() { Name = "Benz", Volume = 20, Price = 150 });
// don't let view binding null value, so here initialze property "SelectedCarViewModel"
this.selectedCarViewModel = ItemsSource[0];
}
}
}
CarViewModel.cs
namespace WpfApp4.ViewModels
{
class CarViewModel : ViewModelBase
{
private string name;
public string Name
{
get { return this.name; }
set { SetProperty(ref this.name, value); }
}
private float volume;
public float Volume
{
get { return this.volume; }
set { SetProperty(ref this.volume, value); }
}
private decimal price;
public decimal Price
{
get { return this.price; }
set { SetProperty(ref this.price, value); }
}
}
}
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
namespace WpfApp4.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T t, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(t, value))
{
return false;
}
else
{
t = value;
RaisePropertyChanged(propertyName);
return true;
}
}
}
}

WPF ListView Selection inside a treeview I Need to get all the selected ListView Items(when Holding CTRL key) in selection changed event

New to WPF
This is Xaml code Need to Get All Selected ListView Items onto selection changed event
I Tried to work with Multiselecttreeview nuget but the problem is both parent and child are same in that case .
Also TreeviewEX does the same .Any help would be great.
<Window x:Class="DemoApps.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:DemoApps"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView x:Name="TreeViewList" Loaded="TreeViewList_Loaded">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding Users}" >
<Grid Width="200" Height="auto">
<StackPanel>
<TextBlock Text="{Binding GroupName}"/>
</StackPanel>
</Grid>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type local:User}">
<ListView SelectionMode="Extended" SelectionChanged="ListView_SelectionChanged">
<Grid Width="150" Height="20">
<StackPanel>
<TextBlock Text="{Binding UserName}" />
</StackPanel>
</Grid>
</ListView>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Window>
This is my code behind
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace DemoApps
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Group> GroupData { get; set; }
public MainWindow()
{
InitializeComponent();
GroupData = GetDummyData();
}
private ObservableCollection<Group> GetDummyData()
{
var _group = new ObservableCollection<Group>();
for (int i = 0; i < 20; i++)
{
_group.Add(new Group
{
GroupName = "Group name" + i,
Users = GetDummyUsers(i)
});
}
return _group;
}
private ObservableCollection<User> GetDummyUsers(int i)
{
var _user = new ObservableCollection<User>();
for (int j = 0; j < 10; j++)
{
_user.Add(new User
{
UserName = "User " + i + "-" + j
});
}
return _user;
}
private void TreeViewList_Loaded(object sender, RoutedEventArgs e)
{
TreeViewList.ItemsSource = GroupData;
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Need to get all selected items from List View.
//Here i need to get the selected items
}
}
public class Group : TreeViewItemBase
{
public string GroupName { get; set; }
public ObservableCollection<User> Users { get; set; }
}
public class User
{
public string UserName { get; set; }
}
public class TreeViewItemBase : INotifyPropertyChanged
{
private bool isSelected;
public bool IsSelected
{
get { return this.isSelected; }
set
{
this.isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
this.isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
How can get all the selected Items of ListView into Selection changed event
The issue is with the data template below:
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type local:User}">
<ListView SelectionMode="Extended" SelectionChanged="ListView_SelectionChanged">
<Grid Width="150" Height="20">
<StackPanel>
<TextBlock Text="{Binding UserName}" />
</StackPanel>
</Grid>
</ListView>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
It is creating a ListView per item so only one item can ever be selected.
Change the XAML to display all the users in a ListView
<Window x:Class="DemoApps.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"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView x:Name="TreeViewList" Loaded="TreeViewList_Loaded">
<TreeView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding GroupName}"/>
<ListView SelectionMode="Extended" SelectionChanged="ListView_SelectionChanged"
ItemsSource="{Binding Users}">
</ListView>
</StackPanel>
</DataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Window>
Get the selected items using
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItems = ((ListView) e.Source).SelectedItems;
}

WPF - Changes in treeviewitems' properties not reflected in UI problem

I have a tree view built with HierarchicalDataTemplates, I want to be able to add JSON files to SegmentInfo nodes - if I do so, the data is added but the change is not reflected in UI (still the comment says "no data" in red).
I've made the list the tree view items as ObservableCollection, moved it to a "ViewModel" class that inherits INotifyPropertyChanged, I seem to set it up properly, I've set DataContext to the ViewModel object in my Window.
In xaml I've set the bindings and mode as TwoWay. Still nothing helped
XAML:
<Window.Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter" FalseValue="no data" TrueValue="has data" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" MinHeight="384.8"/>
<RowDefinition Height="35.2"/>
</Grid.RowDefinitions>
<TreeView Name="trvTypeInfos" Margin="5" Grid.Row="0" ItemsSource="{Binding Path=TypeInfoList, Mode=TwoWay}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="ListBoxItem.PreviewMouseUp"
Handler="ListBoxItem_PreviewMouseUp"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type data:TypeInfo}" ItemsSource="{Binding SegmentInfos, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding SegmentInfos.Count}" Foreground="Blue"/>
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:SegmentInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Path=HasData, Mode=TwoWay, Converter={StaticResource BoolToStringConverter}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="no data">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
<Trigger Property="Text" Value="has data">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="80" Height="20" Content="OK" Margin="5,0, 5, 5" IsDefault="True" Click="OK_Click"/>
<Button Width="80" Height="20" Content="Cancel" Margin="5,0, 5, 5" Click="Cancel_Click" />
</StackPanel>
</Grid>
Window class:
public SegmentDataUpdaterDialog(SegmentDataUpdater segmentDataUpdater, List<TypeInfo> typeInfoList)
{
ViewModel = new ViewModel(typeInfoList);
DataContext = ViewModel;
SegmentDataUpdater = segmentDataUpdater;
InitializeComponent();
}
private void ListBoxItem_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
SegmentInfo segInfo = item.Header as SegmentInfo;
if (segInfo != null)
{
MessageBox.Show(segInfo.JsonContents);
var filePath = AskForFile();
bool success = SegmentDataUpdater.TryStoreJson(segInfo, filePath, out string json);
if (success)
{
segInfo.JsonContents = json;
segInfo.HasData = true;
}
}
}
ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<TypeInfo> _typeInfoList;
public ObservableCollection<TypeInfo> TypeInfoList
{
get { return _typeInfoList; }
set
{
if (_typeInfoList==null || !value.All(_typeInfoList.Contains))
{
_typeInfoList = value;
OnPropertyChanged(nameof(TypeInfoList));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(List<TypeInfo> typeInfos)
{
TypeInfoList = new ObservableCollection<TypeInfo>(typeInfos);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
TypeInfo class:
public class TypeInfo
{
public string Name { get; set; }
public ObservableCollection<SegmentInfo> SegmentInfos { get; set; }
public int ElementId { get; set; }
public TypeInfo()
{
SegmentInfos = new ObservableCollection<SegmentInfo>();
}
}
SegmentInfo class:
public class SegmentInfo
{
public string Name { get; set; }
public bool HasData { get; set; }
public string JsonContents { get; set; }
public int ElementId { get; set; }
}
Converter classes:
public class BoolToValueConverter<T> : IValueConverter
{
public T FalseValue { get; set; }
public T TrueValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseValue;
else
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null ? value.Equals(TrueValue) : false;
}
}
public class BoolToStringConverter : BoolToValueConverter<String> { }
I expect that after successful adding json file to the SegmentInfo the UI will update the node with "has data" comment.
Now I can check that the data is really added to the SegmentInfo but UI doesn't reflect that.
Your HasData property does not update the UI, as you have no mechanism to update it (INotifyPropertyChanged). SegmentInfo needs to implement INotifyPropertyChanged.
If you plan to have a property Bind to the UI, it needs to have an PropertyChanged Notification go out for it individually. So on your SegmentInfo class; Name, HasData, and JsonContent should each raise an OnPropertyChanged event in their setter.
A good way to think of it; anything that is directly bound in XAML (Text="{Binding Name}") should raise an event when changed. If you bind any properties like: (Text="{Binding MyThing.Name}") you will not get an update when MyThing.Name changes. You need to break out the property and Notify on it directly.

Set value of DependencyProperty to ViewModel property

Not sure if what I want is possible, but I also wouldn't know why it isn't.
I have a user control with a dependency property (a string) which I define in XAML e.g. as follows:
<Window ... (EngraveUnitWindow)
DataContext = EngraveUnitViewModel
...
...
<parameters:DoubleParameterUserControl
DisplayName="Exchanger Offset [deg]"
DataContext="{Binding ExchangerOffset}"/>
The view model 'EngraveUnitViewModel' :
public class EngraveUnitViewModel : ViewModelBase, IUnitViewModel
...
...
public DoubleParameterViewModel ExchangerOffset { get; }
What I want to achieve, is set the value of DisplayName to ParameterName property in the DoubleParameterViewModel. So I created a Style which binds the DisplayName to the viewmodel as follows:
<UserControl.Resources>
<Style TargetType="parameters:DoubleParameterUserControl">
<Setter Property="DisplayName" Value="{Binding ParameterName, Mode=OneWayToSource}"/>
</Style>
</UserControl.Resources>
The complete DoubleParameterUserControl code below:
<UserControl
...
...
d:DataContext="{d:DesignInstance viewModels:DoubleParameterViewModel, d:IsDesignTimeCreatable=False}"
Margin="5">
<UserControl.Resources>
<Style TargetType="parameters:DoubleParameterUserControl">
<Setter Property="DisplayName" Value="{Binding ParameterName, Mode=OneWayToSource}"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=DoubleParameter, Path=DisplayName}" VerticalAlignment="Center" Margin="5,0,0,0" />
<Border Grid.Column="1" BorderThickness="1" BorderBrush="LightGray" Margin="0, 0, 5, 0">
<TextBlock
VerticalAlignment="Center"
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Margin="5, 0, 5, 0">
<TextBlock.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding ShowNumpadCommand}" />
</TextBlock.InputBindings>
</TextBlock>
</Border>
<Button x:Name="_button" Grid.Column="2" MinWidth="30" MinHeight="30" Content="..." Command="{Binding ShowNumpadCommand}"/>
</Grid>
</UserControl>
And its code behind (where I define the DependencyProp:
public partial class DoubleParameterUserControl
{
public string DisplayName
{
get => (string)GetValue(DisplayNameProperty);
set => SetValue(DisplayNameProperty, value);
}
public static readonly DependencyProperty DisplayNameProperty =
DependencyProperty.Register(nameof(DisplayName), typeof(string), typeof(DoubleParameterUserControl),
new PropertyMetadata(""));
public DoubleParameterUserControl()
{
InitializeComponent();
_button.Focus();
}
}
For completeness, the viewmodel:
public class DoubleParameterViewModel : ViewModelBase
{
private readonly Parameter<double> parameter;
private double value;
public RelayCommand ShowNumpadCommand { get; }
public string ParameterName { get; set; }
public double Value
{
get => parameter.Value;
set
{
parameter.Value = value;
Set(() => Value, ref this.value, value);
}
}
public DoubleParameterViewModel(Parameter<double> parameter)
{
this.parameter = parameter;
ShowNumpadCommand = new RelayCommand(ShowNumpad);
}
private void ShowNumpad()
{
var numpadViewModel = new VirtualKeypadsViewModel(true)
{
ParameterName = ParameterName,
Input = Value.ToString("F2", CultureInfo.InvariantCulture)
};
var numpad = new Numpad
{
Owner = System.Windows.Application.Current.MainWindow,
DataContext = numpadViewModel
};
if (numpad.ShowDialog() == true)
{
Value = numpadViewModel.ResultAsDouble();
}
}
}
Just to be clear, the property ParameterName in the ViewModel never gets set. So in my code, I want to popup a Numpad dialog which shows the parameter name in its title bar, but the ParameterName did not receive the bound DisplayName.
I hope somebody can explain me how I can solve that. (or, that it is not possible, and why not if that would sadly be the case)
It seems like DoubleParameterViewModel.ParameterName exists solely to provide a name when executing ShowNumpadCommand. If that's the case, forget the property and just pass DisplayName as your command parameter.
public ICommand ShowNumpadCommand { get; }
public DoubleParameterViewModel(Parameter<double> parameter)
{
this.parameter = parameter;
ShowNumpadCommand = new RelayCommand<string>(ShowNumpad);
}
private void ShowNumpad(string parameterName)
{
/* ... */
}
Get rid of the Style, and bind your button's command parameter to its owner's DisplayName:
<UserControl x:Name="EditorRoot">
<!-- ... -->
<Button x:Name="_button"
Command="{Binding ShowNumpadCommand}"
CommandParameter="{Binding ElementName=EditorRoot, Path=DisplayName}" />
<!-- ... -->
</UserControl>

wpf bind to selected listview item and update another list based on that selection

I'm using Visual Studio 2015, I'm trying to teach myself the MVVM pattern, and I'm hitting a road block. My code is loosely based off of Josh Smiths article, I'm using it to help me learn MVVM and create a small app for work in the process.
What I'm trying to accomplish:
I've bound a viewmodel to a listview showing a list of products, each product has a list of "productTemplate" items. In my View I would like this list to populate inside a listbox when a product from my list view is selected. I am implementing INotifyPropertyChanged. I think I'm just missing something simple but I'm not sure.
My code:
Two Models (Product, ProductTemplateItem);
public class Product {
private string _productNum;
private string _productFamily;
public Product() {
}
public string ProductNum { get; set; }
public string ProductFamily { get; set; }
}
public class ProductTemplateItem : ChangeEventHandlerBase {
private string _TemplateItem;
private string _TemplateCode;
public ProductTemplateItem(string templateItem, string templateCode) {
_TemplateItem = templateItem;
_TemplateCode = templateCode;
}
public string TemplateItem {
get { return _TemplateItem; }
set { if(_TemplateItem != value) {
_TemplateItem = value;
OnPropertyChanged("TemplateItem");
}
}
}
public string TemplateCode {
get { return _TemplateCode; }
set {
if (_TemplateCode != value) {
_TemplateCode = value;
OnPropertyChanged("TemplateCode");
}
}
}
public override string DisplayName {
get {
return $"{TemplateItem} - {TemplateCode}";
}
}
}
My ViewModels (Product View Model, brings everything together and adds the product template list, and AlProductsViewModel adds data and exposes everything to be bound in XAML):
public class ProductViewModel : BaseViewModel {
private Product _product;
private bool _isSelected;
private List<ProductTemplateItem> _productTemplate;
public ProductViewModel(string productNum, string productFamily) {
Product.ProductNum = productNum;
Product.ProductFamily = productFamily;
_productTemplate = new List<ProductTemplateItem>();
}
public string ProductNumber {
get { return _product.ProductNum; }
set { if(_product.ProductNum != value) {
_product.ProductNum = value;
OnPropertyChanged("ProductNumber");
}
}
}
public string ProductFamily {
get { return _product.ProductFamily; }
set {
if (_product.ProductFamily != value) {
_product.ProductFamily = value;
OnPropertyChanged("ProductFamily");
}
}
}
public bool IsSelected {
get { return _isSelected; }
set {
if (_isSelected != value) {
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public List<ProductTemplateItem> ProductTemplate {
get { return _productTemplate; }
set { if (_productTemplate != value) {
_productTemplate = value;
OnPropertyChanged("ProductTemplate");
}
}
}
public Product Product {
get {
if (_product == null) {
_product = new Product();
return _product;
}
else {
return _product;
}
}
set { if(_product != value) {
_product = value;
OnPropertyChanged("Product");
}
}
}
public override string DisplayName {
get {
return Product.ProductNum;
}
}
}
public class AllProductsViewModel : BaseViewModel{
public AllProductsViewModel() {
AddProducts();
}
private void AddProducts() {
List<ProductViewModel> all = new List<ProductViewModel>();
all.Add(new ProductViewModel("4835", "Crop Cart"));
all.Add(new ProductViewModel("780", "Piler"));
all.Add(new ProductViewModel("880", "Piler"));
all.Add(new ProductViewModel("150", "Scooper"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Miscellaneous","MISC"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Drawbar", "DRBR"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Mainframe", "FRAM"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Conveyor", "CONV"));
all[1].ProductTemplate.Add(new Model.ProductTemplateItem("Hello", "HELL"));
AllProducts = new ObservableCollection<ProductViewModel>(all);
}
public ObservableCollection<ProductViewModel> AllProducts { get; private set; }
}
And my XAML code which is a user control with a ListView based off of Josh's code and a listbox that needs to be updated based off of the selection in the ListView:
<UserControl x:Class="Parts_Book_Tool.Views.ProductsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Parts_Book_Tool.Views"
xmlns:viewModel="clr-namespace:Parts_Book_Tool.ViewModel"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewModel:AllProductsViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<CollectionViewSource x:Key="ProductGroups" Source="{Binding Path=AllProducts}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ProductFamily"/>
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ProductFamily" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<GroupStyle x:Key="ProductGroupStyle">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
FontWeight="Bold"
Margin="1"
Padding="4,2,0,2"
Text="{Binding Path=Name}"
/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<Style x:Key="MainHCCStyle" TargetType="{x:Type HeaderedContentControl}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Border
Background="{StaticResource Brush_HeaderBackground}"
BorderBrush="LightGray"
BorderThickness="1"
CornerRadius="5"
Margin="4"
Padding="4"
SnapsToDevicePixels="True"
>
<TextBlock
FontSize="14"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
Text="{TemplateBinding Content}"
/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ProductItemsStyle" TargetType="{x:Type ListViewItem}">
<!--
Stretch the content of each cell so that we can
right-align text in the Total Sales column.
-->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<!--
Bind the IsSelected property of a ListViewItem to the
IsSelected property of a CustomerViewModel object.
-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ItemsControl.AlternationIndex" Value="1" />
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#EEEEEEEE" />
</MultiTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<HeaderedContentControl Header="Model Info" Style="{StaticResource MainHCCStyle}" Grid.Row="0">
<ListView x:Name="lvModelNumbers" Margin="6,2,6,50" DataContext="{StaticResource ProductGroups}"
ItemContainerStyle="{StaticResource ProductItemsStyle}" ItemsSource="{Binding}" >
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="ProductGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Model Number" Width="100" DisplayMemberBinding="{Binding Path=DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
</HeaderedContentControl>
<HeaderedContentControl Header="Model Template" Style="{StaticResource MainHCCStyle}" Grid.Row="1">
<ListBox ItemsSource="{Binding SelectedItem/ProductTemplate, ElementName=lvModelNumbers}" Margin="6,2,6,50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</HeaderedContentControl>
</Grid>
It feels to me like I'm missing the capture of an event to update the listbox, but with my inexperience in MVVM I Can't be sure. I've tried binding to the SelectedItem of the named element but that doesn't work. I can get the listbox to populate if I bind "AllProducts/ProductTemplate", but it just gives me the first indexed values, and doesn't dynamically change when I select another product.
Hopefully that is enough information, and any help would be greatly appreciated. I'm enjoying learning MVVM but it's dificult to wrap my head around.
Thanks,
Try bind the ListBox to the ProductTemplate property of the SelectedItem property of the ListView:
<ListView x:Name="lvModelNumbers" Margin="6,2,6,50"
ItemsSource="{Binding Source={StaticResource ProductGroups}}"
ItemContainerStyle="{StaticResource ProductItemsStyle}">
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="ProductGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Model Number" Width="100" DisplayMemberBinding="{Binding Path=DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
<ListBox ItemsSource="{Binding SelectedItem.ProductTemplate, ElementName=lvModelNumbers}" Margin="6,2,6,50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories

Resources