WPF datagrid dropdown two-way binding - wpf

I'm trying to get a dropdown column in a WPF datagrid (hosted in a user control derived from a base class called ControlBase) to bind properly. It initially populates fine from the object, and a populated dropdown appears when I edit the cell, but a selected value does not get back into the cell when I leave focus.
Here's my model and domain objects:
public class ModelBase : INotifyPropertyChanged
{
public IList<Person> Persons { get; set; }
}
public class UserControlModel : ModelBase
{
public ObservableCollection<DatagridRecord> SourceData { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DatagridRecord
{
public string Name { get; set; }
public Person ContactPerson { get; set; }
}
And in my xaml.cs, I set the DataContext via a Model property:
public UserControlModel _model;
public UserControlModel Model
{
set
{
_model = value;
DataContext = null;
DataContext = _model;
}
}
Here's my DataGrid column definition in xaml:
<DataGridTemplateColumn Header="Person" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ContactPerson.Name}"/></DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Persons,
RelativeSource={RelativeSource AncestorType={x:Type uch:ControlBase}}}"
DisplayMemberPath="Name"
SelectedValuePath="Id" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
I figure there is something wrong with tying the combobox selected value to the grid row, but I've gone around in circles trying to hook it up. Any advice would be appreciated.
Corey.

You're Missing a SelectedItem or SelectedValue binding:
<ComboBox ItemsSource="{Binding Path=DataContext.Persons,
RelativeSource={RelativeSource AncestorType={x:Type uch:ControlBase}}}"
DisplayMemberPath="Name"
--> SelectedItem="{Binding ContactPerson}" <--
SelectedValuePath="Id" />

Related

WPF: Access SelectedItem of nested DataGrid

I have a nested DataGrid setup. How do I access the SelectedItem for the inner grid?
<DataGrid ItemsSource="{Binding OuterGrid}" RowDetailsVisibilityMode="Visible">
<DataGrid.Columns>
<DataGridCheckBoxColumn ..... />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding InnerGrid}" SelectedItem="{Binding SelectedInner}">
<DataGridTextColumn ..... />
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
SelectedInner is a property and even if I select a row in the InnerGrid in the UI, that property is still null.
How do I know which row was selected in the inner grid? I also use caliburn-micro.
It depends on where the 'SelectedInner' object is in your ViewModel. If the the object is in your ViewModel of your view (as is in the example below), you need to use RelativeResource in order to point out the datagrid where your bound object is.
class ShellViewModel:Screen
{
public BindableCollection<DataGridObject> OuterGrid { get; set; } = new BindableCollection<DataGridObject>();
public BindableCollection<DataGridObject> InnerGrid { get; set; } = new BindableCollection<DataGridObject>();
public DataGridObject selectedInner { get; set; } = new DataGridObject();
public ShellViewModel()
{
OuterGrid.Add(new DataGridObject {String1="water",String2="air",String3="earth" });
OuterGrid.Add(new DataGridObject {String1="water1",String2="air2",String3="earth2" });
OuterGrid.Add(new DataGridObject { String1 = "water3", String2 = "air3", String3 = "earth3" });
InnerGrid.Add(new DataGridObject {String1="water",String2="air",String3="earth" });
InnerGrid.Add(new DataGridObject {String1="water1",String2="air2",String3="earth2" });
InnerGrid.Add(new DataGridObject {String1="water3",String2="air3",String3="earth3" });
NotifyOfPropertyChange(() => OuterGrid);
NotifyOfPropertyChange(() => InnerGrid);
}
}
public class DataGridObject
{
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
}
On the xaml you need to use RelativeResource
<DataGrid ItemsSource="{Binding OuterGrid}" RowDetailsVisibilityMode="Visible">
<!--<DataGrid.Columns>
<DataGridCheckBoxColumn />
</DataGrid.Columns>-->
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel>
<DataGrid ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},Path=DataContext.InnerGrid}" SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},Path=DataContext.selectedInner}" >
</DataGrid>
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Make sure you specify the correct ancestor which ma be window or usercontrol

How to Bind a Dictionary with the Key value being a Binding Property in WPF?

I have a class like this:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int AccountId { get; set; }
public Dictionary<int, List<string>> Values { get; set; }
}
I have a DataGrid in my XAML where I want to display the first index of the List<string> within the dictionary property Values as one of the column values, where the key being passed in will be the AccountId. The ItemSource of my DataGrid is a list of Person objects coming from my ViewModel, and the DataGrid has 3 columns, PersonId, Name, and Value (where value is the first index of the List<string> collection in the dictionary item)
I have seen examples on stackoverflow and other places on the internet that try to do this but none of the solutions worked for me.
Here is my XAML code:
<DataGrid Name="MyDataGrid" ItemsSource="{Binding Persons}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ID">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding PersonId}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Values[{Binding AccountId}][0]}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The last column is the column where I try to use a {Binding} as the key value, but it does not work. If I hard-code a valid AccountId, it works. Has anyone encountered this before?
Thanks!
one of view model purposes is to provide data to view in a convenient format. code to get value from dictionary by key and then return first item can be written in a view model, not in converter:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int AccountId { get; set; }
public Dictionary<int, List<string>> Values { get; set; }
public string AccountIdValue { get { return Values[AccountId].FirstOrDefault(); } }
}
then bind to that helper property:
<TextBox Text="{Binding AccountIdValue}" IsEnabled="False" />

WPF DataGrid ContentControll bindng

I want to make DataGrid with a structure as in image below.
To have two TextBoxes in each cell.
Ive made Class
public class ComplexTable : ViewModelBase
{
public ComplexTable()
{
FirstProperty = new FirstClass();
SecondProperty = new Second();
}
public class FirstClass
{
public FirstClass()
{
First = "FirstString";
Second = "SecondString";
}
public string First { get; set; }
public string Second { get; set; }
}
public class Second
{
public Second()
{
Third = "ThirdString";
Fourth = "FourthString";
}
public string Third { get; set; }
public string Fourth { get; set; }
}
public FirstClass FirstProperty { get; set; }
public Second SecondProperty { get; set; }
}
public ObservableCollection<ComplexTable> _testCollection = new ObservableCollection<ComplexTable>();
private ObservableCollection<ComplexTable> TestCollection
{
get { return _testCollection; }
set
{
_testCollection = value;
RaisePropertyChanged("TestCollection");
}
}
And a TestCollection that should be a ItemsSource for DataGrid.
My DataGrid
<DataGrid CanUserAddRows="True"
ItemsSource="{Binding TestCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text=" "/>
<TextBox Text=" "/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Second Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text=" "/>
<TextBox Text=" "/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I can't figure out how to bind those textboxes. Or i went in a wrong direction?
The datagrid sets the DataContext of the columns to the elements of the ItemSource. Every element in that ItemSource will be displayed as one Row. In your case the ItemSource is TestCollection. Therefore the DataContext inside your DataGridTemplateColumn is set to the elements of the TestCollection. If TestCollection contains ComplexTable elements. You can bind directly to the properties on ComplexTable.
var TestCollection = new ObservableCollection<DataForOneRow> {DataForFirstRow, DataForSecondRow, DataForThirdRow};
public class DataForOneRow {
public string DataForFirstColumnFirstTextBox {get; set;} //left out raise of PropertyChanged for brevity
public string DataForFirstColumnSecondTextBox {get; set;}
public string DataForSecondColumnFirstTextBox {get; set;}
public string DataForSecondColumnSecondTextBox {get; set;}
}
<DataGridTemplateColumn Header="First Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text="{Bidning DataForFirstColumnFirstTextBox}"/>
<TextBox Text="{Bidning DataForFirstColumnSecondTextBox}"/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
FYI: You have to raise a PropertyChanged event in all ViewModel properties.

How to bind to a child branch or leaf of a treeview (in MVVM style)?

I'm a newbie to TreeViews. In WPF style, I have a TreeView organized in three levels:
ReportName1
NetworkName1
PrinterName1
PrinterName2
NetworkName2
PrinterName3
PrinterName4
ReportName2
....
in the XAML, I am using Interaction behaviors to bind the TreeView SelectedItem to the ViewModel:
<TreeView ItemsSource="{Binding ReportTree}" >
<i:Interaction.Behaviors>
<tvb:TreeViewBehavior SelectedItem="{Binding SelectedTreeItem, Mode=TwoWay}" />
</i:Interaction.Behaviors>
At this point, all works well to send an item from the ReportTree when I select ANY item under the main report name. That is, if I select PrinterName2, then the SelectedTreeItem will be the main viewmodel for ReportName1.
What I need to know is, how can I tell that PrinterName2 is selected as opposed to PrinterName1?
My eventual goal is to allow selection of any leaf or branch in the tree and remove only that selected leaf or branch.
Is there a way to do this?
Thanks for any help on this.
Here is one option to solve this using a simple DataTemplate for the TreeView which contains a MouseBinding to call a select command on the parent ViewModel and pass the clicked item as a CommandParameter.
If your ViewModel looks something like this:
public class MainViewModel
{
public ObservableCollection<ItemViewModel> Items { get; private set; }
public ItemViewModel SelectedItem { get; set; }
public ICommand SelectItem { get; private set; }
public MainViewModel()
{
SelectItem = new LazyCommand<ItemViewModel>(ExecuteSelect);
Items = new ObservableCollection<ItemViewModel>();
}
private void ExecuteSelect(ItemViewModel item)
{
SelectedItem = item;
}
}
with a straightforward ViewModel for the items:
public class ItemViewModel
{
public ObservableCollection<ItemViewModel> Items { get; private set; }
public string Name { get; set; }
public ItemViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
}
Then you can define a TreeView with an HierarchicalDataTemplate as ItemTemplate:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}" >
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.SelectItem, RelativeSource={RelativeSource FindAncestor, AncestorType=TreeView}}"
CommandParameter="{Binding}" />
</TextBlock.InputBindings>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The crucial part is the binding to the TreeViews DataContext.SelectItemCommand from the MouseBinding of each item and passing the item in as a parameter. You can then handle the selection itself (setting SelectedItem, etc.) in the ViewModel.

Silverlight - binding a listbox within a listbox template

In silverlight, I'm trying to build a listbox of reports and I'm trying to show the parameters of the report in a listbox within the datatemplate of the outer reports listbox.
Here are the data classes:
public class Report
{
public string Title { get; set; }
public string Description { get; set; }
public List<ReportParameter> Parameters = new List<ReportParameter>();
}
public class ReportParameter
{
public string Name { get; set; }
public string ParameterType { get; set; }
public bool Required { get; set; }
}
Here's the XAML I'm trying to use to do it:
<ListBox x:Name="lstReports">
<ListBox.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<ListBox ItemsSource="{Binding Parameters}" Height="60" Width="60">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The binding for the Title of the report works, but the inner listbox is empty for each of the reports.
The Parameters list is populated.
What am I doing wrong?
Thanks!
Your "Parameters" list is a public field not a property, silverlight can only bind to properties and not fields. Try changing your class to this:
public class Report
{
public Report()
{
Parameters = new List<ReportParameter>();
}
public string Title { get; set; }
public string Description { get; set; }
public List<ReportParameter> Parameters { get; set; }
}
This should work the way you want it.

Resources