TreeViewItem with Databinding, Childs doesn't update - wpf

I've got a Treeview with a hierachial template.
Everything works fine. All Objets will respond as expected.
But adding elements to the collection doesn't update the treeview.
My base Object is bind to the treeview.
One of its propertys contains a collection. And this collection has got a property with an own collection.
BaseObject
-> Sub Collection 1
-> SubCollection 2
My BaseObject has implemented INotifyPropertyChanged and my SubCollection 2 has implemented ICollectionChaged.
Nevertheless, wehen I try to add a new Item to SubCollection 2 OnCollectionChaged is called, but CollectionChanged stays null, so nothing happens.
TreeView Templates:
<HierarchicalDataTemplate x:Key="SheetTreeTemplate" >
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="/Resources/Icons/page_green.png" />
<TextBlock FontStyle="Italic">
<TextBlock.Text>
<MultiBinding StringFormat="{}Seite {0}">
<Binding Path="Name"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="DocumentTreeTemplate" ItemsSource="{Binding Path=Sheets.Values}" ItemTemplate="{StaticResource SheetTreeTemplate}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="/Resources/Icons/folder.png" />
<TextBlock FontStyle="Italic">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="DocTypName"/>
<Binding Path="ID"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="PileTreeTemplate" ItemsSource="{Binding Path=Documents.Values}" ItemTemplate="{StaticResource DocumentTreeTemplate}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="/Resources/Icons/report.png" />
<TextBlock FontStyle="Italic" Text="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
TreeView itself:
<TreeView Style="{DynamicResource NavigationTree}" Name="tvw_mainMenu" ItemsSource="{Binding Values}" ItemTemplate="{DynamicResource PileTreeTemplate}" SelectedItemChanged="tvw_mainMenu_SelectedItemChanged"/>
the Class which should subscribe the SubCollection 2 Changed:
class Sheets : Dictionary<String, Sheet> , INotifyCollectionChanged {
public bool Add(String sKey, Sheet newSheet) {
base.Add(sKey, newSheet);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new KeyValuePair<String, Sheet>(sKey, newSheet)));
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
if (CollectionChanged != null) {
CollectionChanged(this, e);
}
}
}

i've found out that there are much more interfaces to implement. Best way is an observable Collection.
Cause I don't want to change all my classes i've found ObservableDictionary
example.

Related

WPF Hierarchical Treeview: Combined binding of a selfrefering hierarchical and a flat source

Recently, I learned a lot about MVVM / Binding / Entityframework etc. And as I always go for the hard way - I use VB.NET - and have to convert most of the code found from C# to VB.NET ;)
So, what's my Topic:
Complete Titel:
WPF Hierarchical Treeview: Combined binding and templating of an selfrefering hierarchical source and a flat source with EntityFramework 6 and Database First Approach.
DataModel:
Image: https://i.stack.imgur.com/LIb1v.png
Expected Treeview
I have two types of Items:
"Dimensions" are from a selfrefering Source (XELL_DIMENSION) and
hierarchical Items. unknown/open Leveldepth.
"Elements" are from a flat Source (XELL_ELEMENTS) a
Image: https://i.stack.imgur.com/QeFwe.png
Ok here's what I have achieved so far:
Mainwindow Class:
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim elementsContext As New XELLEntities()
Tree.DataContext = elementsContext.XELL_DIMENSION.Include("XELL_ELEMS").ToList()
Tree.ItemsSource = elementsContext.XELL_DIMENSION.Where(Function(y) y.DIMEN_PARENT_ID Is Nothing).ToList()
End Sub
XAML CODE:
<TreeView Name="Tree" HorizontalAlignment="Left" Height="187" Margin="10,10,0,0" VerticalAlignment="Top" Width="415">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local2:XELL_DIMENSION}" ItemsSource="{Binding DIM_ALL_NODE}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DIMEN_ID}"/>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding DIMEN_BEZ_LONG}"/>
<ListBox Name="Listy" ItemsSource="{Binding XELL_ELEMS}" DisplayMemberPath="ELEM_BEZ_LONG" BorderBrush="Transparent" BorderThickness="0"/>
</StackPanel>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Result:
Question:
So that's the closest I came and I used a Listbox for the Elements - but this
is no solution to me.
- How do I solve my Problem?
I am thankful for any CodeSnipped provided.
Eureka! -SOLVED
I had to use a Converter and a TemplateSelector.
XAML:
<TreeView Name="Tree" HorizontalAlignment="Left" Height="187" Margin="10,10,0,0" VerticalAlignment="Top" Width="415"> <TreeView.Resources>
<local3:LeafDataTemplateSelector x:Key="LeafDataTemplateSelector" />
<HierarchicalDataTemplate DataType="{x:Type local2:XELL_DIMENSION}" ItemTemplateSelector="{StaticResource LeafDataTemplateSelector}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource SimpleFolderConverter}">
<Binding Path="DIM_ALL_NODE" />
<Binding Path="XELL_ELEMS" />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Orientation="Horizontal">
<Image HorizontalAlignment="Center" Height="20" VerticalAlignment="Center" Width="20" Source="MVVM/VIEW/IMAGES/Nodes.png" Stretch="Uniform" />
<TextBlock Foreground="#FF3399FF" Text="{Binding DIMEN_BEZ_LONG}" FontWeight="Bold"/>
</StackPanel>
</HierarchicalDataTemplate> <HierarchicalDataTemplate x:Key="Dimension" DataType="{x:Type local2:XELL_DIMENSION}" ItemTemplateSelector="{StaticResource LeafDataTemplateSelector}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource SimpleFolderConverter}">
<Binding Path="XELL_ELEMS" />
<Binding Path="DIM_ALL_NODE" />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation File">
<Image HorizontalAlignment="Center" Height="20" VerticalAlignment="Center" Width="20" Source="MVVM/VIEW/IMAGES/Nodes.png" Stretch="Uniform" />
<TextBlock Foreground="#FF3399FF" Text="{Binding DIMEN_BEZ_LONG}" FontWeight="Bold"/>
</StackPanel>
</HierarchicalDataTemplate> <DataTemplate x:Key="Element" DataType="{x:Type local2:XELL_ELEMENT}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation File">
<Image HorizontalAlignment="Center" Height="20" VerticalAlignment="Center" Width="20" Source="MVVM/VIEW/IMAGES/Shape57.png" Stretch="Uniform" />
<TextBlock Foreground="DarkGray" Text="{Binding ELEM_BEZ_LONG}" FontWeight="Normal" FontStyle="Italic"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
VB.NET - MainWindow
Public Sub New()
' Dieser Aufruf ist für den Designer erforderlich.
InitializeComponent()
' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu.
End Sub
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim elementsContext As New XELLEntities()
Tree.DataContext = elementsContext.XELL_DIMENSION.Include("XELL_ELEMS").ToList()
Tree.ItemsSource = elementsContext.XELL_DIMENSION.Where(Function(y) y.DIMEN_PARENT_ID Is Nothing).ToList()
End Sub
VB.NET TemplateSelection
Public Class LeafDataTemplateSelector
Inherits DataTemplateSelector
Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate
Dim element As FrameworkElement = TryCast(container, FrameworkElement)
If element IsNot Nothing AndAlso item IsNot Nothing Then
If TypeOf item Is XELL_DIMENSION Then
Return TryCast(element.FindResource("Dimension"), DataTemplate)
ElseIf TypeOf item Is XELL_ELEMENT Then
Return TryCast(element.FindResource("Element"), DataTemplate)
End If
End If
Return Nothing
End Function
End Class
VB.NET Hierarchy Converter
Class HierarchyConverter : Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Dim node = TryCast(value, Employee)
If node IsNot Nothing Then
Return node.Subordinates.Where(Function(i) i.ManagerID = node.EmployeeID).ToList()
Else
End If
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotSupportedException
End Function End Class
Hope this helps somebody ;)

Bind Multibinding Textbox in WPF MVVM

I have 3 TextBoxes bind with my class(Transaction) properties like this
<TextBox Text="{Binding Path=Transaction.Bills100,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills100" Grid.Column="2" Grid.Row="1" Margin="7"></TextBox>
<TextBox Text="{Binding Path=Transaction.Bill50,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills50" Grid.Column="2" Grid.Row="2" Margin="7"></TextBox>
<TextBox Text="{Binding Path=Transaction.Bill20,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills20" Grid.Column="2" Grid.Row="3" Margin="7"></TextBox>
Also I have another TextBox where I have done multibinding and done addition of the first three Textboxes like
<TextBox Grid.Column="2" IsReadOnly="True" Grid.Row="7" Grid.ColumnSpan="2" Margin="7" Name="TotalBills">
<TextBox.Text>
<MultiBinding Converter="{ikriv:MathConverter}" ConverterParameter="x+y+z" Mode="TwoWay">
<Binding Path="Text" ElementName="bills100" />
<Binding Path="Text" ElementName="bills50" />
<Binding Path="Text" ElementName="bills20" />
</MultiBinding>
</TextBox.Text>
</TextBox>
I want to bind this multibinding textbox with my class(Transaction) with property as Transaction.Total like my first three textboxes but it shows error
Property text is set more than once
Actually we cannot get the value of a two-way binding from one property and then set the value of another property.
Finally I came with a solution like this
In my Class Transaction
private double _totalBills;
public double TotalBills
{
get { return _totalBills; }
set { _totalBills= value; Notify("TotalBills"); }
}
In XAML(Instead of Multibinding)
<TextBox Text="{Binding Path=Transaction.TotalBills,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Column="2" IsReadOnly="True" Grid.Row="7" Grid.ColumnSpan="2" Margin="7" Name="TotalBills"/>
My ViewModel
public class MainViewModel: INotifyPropertyChanged
{
private Transaction _transactionDetails;
public MainViewModel()
{
Transaction= new Transaction();
_transactionDetails.PropertyChanged += _transactionDetails_PropertyChanged;
}
private void _transactionDetails_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "TotalBills":
_calculate(); //My method for calculation
break;
}
}
}

One combo box filtering another combo box in a WPF Datagrid

I have a datagrid which has two combo box columns in it. The first combo box is a list of PersonnelTypes. Depending on which PersonnelType is selected, the second combo box should fill up with a list of Resources that match the selected PersonnelType
The problem is, lets say I have two rows of data, if I change the PersonnelType of one row, the datagrid will set the itemsource for all of the Resources in every row. I only want it to filter the row that I am in, not all the rows.
Here's the xaml for the part of the datagrid that has the combo boxes:
<DataGridTemplateColumn Header="Personnel Type" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelTypes" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" SelectionChanged="cmbPersonnelTypes_SelectionChanged" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelName" FontWeight="Bold" ItemsSource="{Binding ViewModel.ResourcesToChooseFrom, RelativeSource={RelativeSource AncestorType=Window},UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Resource, Mode=TwoWay}" SelectedValuePath="Refno" DisplayMemberPath="Name" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is the xaml for the whole data grid (just in case you need to see it):
<DataGrid AutoGenerateColumns="False" CanUserSortColumns="False" CanUserDeleteRows="True" IsReadOnly="True" Background="LightGray" CanUserAddRows="False" Margin="5" SelectedItem="{Binding SelectedLA_JobPersonnel}" ItemsSource="{Binding LA_Personnel}" Grid.ColumnSpan="4" MouseDoubleClick="DataGrid_MouseDoubleClick_1">
<DataGrid.Resources>
<ViewModels:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Personnel Type" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelTypes" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" SelectionChanged="cmbPersonnelTypes_SelectionChanged" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelName" FontWeight="Bold" ItemsSource="{Binding ViewModel.ResourcesToChooseFrom, RelativeSource={RelativeSource AncestorType=Window},UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Resource, Mode=TwoWay}" SelectedValuePath="Refno" DisplayMemberPath="Name" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> <DataGridTemplateColumn Header="Date Out" Width="20*" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Background="LightGray" FontWeight="Bold" Text="{Binding DateOut, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}, StringFormat={}{0:MMM-dd-yyyy hh:ss tt}}">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Toolkit:DateTimePicker Background="LightGray" FontWeight="Bold" Value="{Binding Path=DateOut, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}}" Format="Custom" FormatString="MMM dd yyyy hh:ss tt"></Toolkit:DateTimePicker>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Date In" Width="20*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Background="LightGray" FontWeight="Bold" Text="{Binding DateIn, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}, StringFormat={}{0:MMM-dd-yyyy hh:ss tt}}">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Toolkit:DateTimePicker Background="LightGray" FontWeight="Bold" Value="{Binding Path=DateIn, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}}" Format="Custom" FormatString="MMM dd yyyy hh:ss tt"></Toolkit:DateTimePicker>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Here is the code behind for the xaml (xaml.cs):
public JobEditorViewModel ViewModel
{
get { return viewModel; }
}
private void cmbPersonnelTypes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var combobox = sender as ComboBox;
if (combobox != null)
{
var selectedPersonnelType = combobox.SelectedItem as PersonnelType;
viewModel.SetResourcesToChooseFrom(selectedPersonnelType);
}
}
Here is the code in the viewModel:
public BindingList<PersonnelType> PersonnelTypes
{
get; set;
}
public JobEditorViewModel(int jobid, string region, DataAccessDataContext db, ServiceUserControlViewModel serviceViewModel)
{
PersonnelTypes = new BindingList<PersonnelType>(_db.PersonnelTypes.OrderBy(p => p.Head).ThenBy(p => p.Description).ToList());
}
private BindingList<Resource> _resourcesToChooseFrom;
public BindingList<Resource> ResourcesToChooseFrom
{
get { return _resourcesToChooseFrom; }
set
{
_resourcesToChooseFrom = value;
NotifyPropertyChanged("ResourcesToChooseFrom");
}
}
public void SetResourcesToChooseFrom(PersonnelType personnelType)
{
ResourcesToChooseFrom =
new BindingList<Resource>(_db.Resources.Where(r => r.Head == personnelType.Head && r.Refno > 2).OrderBy(r=>r.Name).ToList());
}
If you need to see more, let me know
Well, with some help from a colleague here at work, we figured out what I needed to do. Multibinding is the answer. First off we kind of hacked around so that the two combo boxes could be in the same column by placing them both in a grid and placing the grid in the one column. So now both combo boxes can see each other because they are in the same DataGridTemplateColumn. Before, we couldn't get them to see each other because they lost scope of each other in being two separate DataGridTemplateColumns.
Here's what we did in the xaml:
<DataGridTemplateColumn Header="Personnel Type-Name" Width="Auto" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding PersonnelType.Description}"/>
</Border>
<Border Grid.Column="1" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding Resource.Name}"/>
</Border>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox Name="cmbPersonnelTypes" Grid.Column="0" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" />
<ComboBox Name="cmbPersonnelName" Grid.Column="1" FontWeight="Bold" SelectedItem="{Binding Resource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Refno" DisplayMemberPath="Name" >
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource FilteredPersonnelConverter}">
<Binding Path="ViewModel.AvailablePersonnel" RelativeSource="{RelativeSource AncestorType=Window}"/>
<Binding Path="SelectedItem" ElementName="cmbPersonnelTypes"/>
<Binding Path="ViewModel.SelectedGlobalResourceViewOption" RelativeSource="{RelativeSource AncestorType=Window}"/>
</MultiBinding>
</ComboBox.ItemsSource>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
You'll notice there is a ValueConverter in the MultiBinding called FilteredPersonnelConverter. This value converter does all the filtering for me. Here's the code for that:
public class FilteredPersonnelListValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var allResources = values[0] as IList<Resource>;
var personnelType = values[1] as PersonnelType;
var selectedGlobalResourceView = values[2] as ResourceViewOption;
if (personnelType == null)
return allResources;
if(selectedGlobalResourceView.ResourceViewTitle=="Regional")
return allResources.Where(r => r.Head == personnelType.Head && r.Obsolete == false && r.Location.Region.RegionID.Trim()==SettingsManager.OpsMgrSettings.Region.Trim()).OrderBy(r => r.Name).ToList();
if (selectedGlobalResourceView.ResourceViewTitle == "Local")
return allResources.Where(r => r.Head == personnelType.Head && r.Obsolete == false && r.LocnID.Trim() == SettingsManager.OpsMgrSettings.LOCNCODE.Trim()).OrderBy(r => r.Name).ToList();
return allResources.Where(r => r.Head == personnelType.Head &&r.Obsolete==false).OrderBy(r => r.Name).ToList();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
So if anyone else is doing something like this, look into Multibinding, it will change your life
When the user changes the PersonelType dropdown in the view, the ViewModel should then filter the list of Resources (which should update the second dropdown box with the correct information).
Really your view model just needs to be set up to respond to changes on the view. That's what it boils down to.
Looking here:
public JobEditorViewModel(int jobid, string region, DataAccessDataContext db, ServiceUserControlViewModel serviceViewModel)
{
PersonnelTypes = new BindingList<PersonnelType>(_db.PersonnelTypes.OrderBy(p => p.Head).ThenBy(p => p.Description).ToList());
}
it looks like you set the personel types in the constructor, but you aren't responding to user changes. You need to do that in the PersonelType Binding in your view model.
EDIT
I see now that you're handling the selection changed. But really I think this should be done in the view model itself. Specifically because you actually access the viewmodel from the view to do make your changes.
Example
So here's my VM:
class ViewModel : INotifyPropertyChanged
{
DispatcherTimer timer = new DispatcherTimer();
public ViewModel()
{
// Create my observable collection
this.DateTimes = new ObservableCollection<DateTime>();
// Every second add anothe ritem
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
// This adds to the collection
this.DateTimes.Add(DateTime.Now);
}
public ObservableCollection<DateTime> DateTimes { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
}
and My View:
<ListBox ItemsSource="{Binding DateTimes}"/>
Notice that I don't rebuild the collection. I just set it once, and then change it's size as neccesary.

displaying ComboBox.SelectedValue in TextBox

I am working on a WPF and have hit a serious wall. I have a data set that has two columns, ContactName and ContactTitle. I have successfully loaded all of the data into a ComboBox and even sorted it by ContactName. However, I am trying to now access that data and display part of it in a TextBox. (This is of course just a proof of concept type exercise, the final product will populate a variety of TextBoxes with the selected persons information). The problem is, I cannot get the info to populate in the TextBox. Here is the code that I have:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
namespace MultiBindingInWPF_CS
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
//Create DataSet
CustomersDataSet customerDataSet = new CustomersDataSet();
//Create DataTableAdapter
CustomersDataSetTableAdapters.CustomersTableAdapter taCustomers = new CustomersDataSetTableAdapters.CustomersTableAdapter();
taCustomers.Fill(customerDataSet.Customers);
//Sort Data
SortDescription sd = new SortDescription("ContactName", ListSortDirection.Descending);
//Designate ItemSource
this.ComboBox1.ItemsSource = customerDataSet.Customers;
//Apply Sort
this.ComboBox1.Items.SortDescriptions.Add(sd);
}
private void ComboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
//Using SelectedIndex only to prove connection to TextBox is working
textBox1.Text = ComboBox1.SelectedIndex.ToString();
}
catch
{
textBox1.Text = "Invalid";
}
}
}
}
Then here is my XAML:
<Window x:Class="MultiBindingInWPF_CS.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="Multibinding in WPF" Height="163" Width="300">
<Grid Loaded="Grid_Loaded">
<StackPanel Name="StackPanel1" Margin="12">
<Label Height="28" Name="Label1">List of Customers (Name AND Title :-) )</Label>
<ComboBox Height="23" Name="ComboBox1" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" IsTextSearchEnabled="True" SelectionChanged="ComboBox1_SelectionChanged" SelectedValue="{Binding Path=CustomerID}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="ContactName" />
<Binding Path="ContactTitle" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Height="23" Name="textBox1" Width="120"/>
</StackPanel>
</Grid>
</Window>
My ultimate goal would be to populate the TextBox dynamically by getting the selected value, and getting the info in the dataset associated with that CustomerID, but just getting the SelectedItem's text to populate in the TextBox would be a huge step.
Any help is GREATLY appreciated. Thanks all.
Give this a try; it removes the changed event handler and leverages binding.
<Grid Loaded="Grid_Loaded">
<StackPanel Name="StackPanel1" Margin="12">
<Label Height="28" Name="Label1">List of Customers (Name AND Title :-) )</Label>
<ComboBox Height="23" Name="ComboBox1" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" IsTextSearchEnabled="True" SelectedValue="{Binding Path=CustomerID}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="ContactName" />
<Binding Path="ContactTitle" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Height="23" Name="textBox1" Text="{Binding ElementName=ComboBox1, Path=SelectedItem.ContactName}" Width="120"/>
</StackPanel>
</Grid>
Check out this SO answer as well, which details the differences between SelectedItem, SelectedValue, and SelectedValuePath and is ultimately the issue most people run into.

Can I do Text search with multibinding

I have below combo box in mvvm-wpf application. I need to implement "Text search" in this..(along with multibinding). Can anybody help me please.
<StackPanel Orientation="Horizontal">
<TextBlock Text="Bid Service Cat ID"
Margin="2"></TextBlock>
<ComboBox Width="200"
Height="20"
SelectedValuePath="BidServiceCategoryId"
SelectedValue="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.SelectedBidServiceCategoryId.Value}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.BenefitCategoryList}"
Margin="12,0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId" />
<Binding Path="BidServiceCategoryName" />
</MultiBinding>
</TextBlock.Text></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
Unfortunately, TextSearch.Text doesn't work in a DataTemplate. Otherwise you could have done something like this
<ComboBox ...>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="TextSearch.Text">
<Setter.Value>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId"/>
<Binding Path="BidServiceCategoryName"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
However this won't work, so I see two solutions to your problem.
First way
You set IsTextSearchEnabled to True for the ComboBox, override ToString in your source class and change the MultiBinding in the TextBlock to a Binding
Xaml
<ComboBox ...
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
Source class
public class TheNameOfYourSourceClass
{
public override string ToString()
{
return String.Format("{0}: {1}", BidServiceCategoryId, BidServiceCategoryName);
}
//...
}
Second Way
If you don't want to override ToString I think you'll have to introduce a new Property in your source class where you combine BidServiceCategoryId and BidServiceCategoryName for the TextSearch.TextPath. In this example I call it BidServiceCategory. For this to work, you'll have to call OnPropertyChanged("BidServiceCategory"); when BidServiceCategoryId or BidServiceCategoryName changes as well. If they are normal CLR properties, you can do this in set, and if they are dependency properties you'll have to use the property changed callback
Xaml
<ComboBox ...
TextSearch.TextPath="BidServiceCategory"
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId" />
<Binding Path="BidServiceCategoryName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
Source class
public class TheNameOfYourSourceClass
{
public string BidServiceCategory
{
get
{
return String.Format("{0}: {1}", BidServiceCategoryId, BidServiceCategoryName);
}
}
private string m_bidServiceCategoryId;
public string BidServiceCategoryId
{
get
{
return m_bidServiceCategoryId;
}
set
{
m_bidServiceCategoryId = value;
OnPropertyChanged("BidServiceCategoryId");
OnPropertyChanged("BidServiceCategory");
}
}
private string m_bidServiceCategoryName;
public string BidServiceCategoryName
{
get
{
return m_bidServiceCategoryName;
}
set
{
m_bidServiceCategoryName = value;
OnPropertyChanged("BidServiceCategoryName");
OnPropertyChanged("BidServiceCategory");
}
}
}
I don't know if your text search has to search ALL the text, but if you want to search from the category ID, you can just set the TextSearch.TextPath property to BidServiceCategoryId. That should also be helpful for anyone who wants to use multibinding and finds that the text search no longer works... It does work if you explicitly set the TextPath property.

Resources