WPF: Access SelectedItem of nested DataGrid - wpf

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

Related

Bind Unrelated property to DataGrid

EDIT: Solved
(I made another property in the ViewModel wrapper and bound to that)
I am trying to bind a property that is not related to the ObservableCollection that the DataGrid is bound to. The other columns are binding the way they should, it is just this one column that I can't seem to get to work.
I tried binding the property using RelativeSource AncestorType and directly to the DataContext with no luck.
The XAML, The ObservableCollection I am binding to obviously is called MonthlyRecords which is a collection of a different class and this is binding the way it should be. It is the property SelectedTenant.FullName which has nothing to do with the collection that is giving me grief.
<DataGrid ItemsSource="{Binding MonthlyRecords}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--Trying to bind this Property in the next line-->
<TextBlock Text="{Binding Path=SelectedTenant.FullName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="60" Header="Code" Binding="{Binding UpdateSourceTrigger=LostFocus, Path=TenantCode}" />
This is the class for the property I am trying to bind.
public class Tenant
{
public Tenant()
{
}
public int Code { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string FullName => LastName + " " + FirstName;
public string Section { get; set; }
public Tenant(int code, string lastName = null, string firstName = null, string section = null)
{
Code = code;
LastName = lastName;
FirstName = firstName;
Section = section;
}
}
And this is the property in the ViewModel I am trying to bind to.
private Tenant _selectedTenant;
public Tenant SelectedTenant
{
get { return _selectedTenant; }
set
{
if (Equals(_selectedTenant, value)) return;
_selectedTenant = value;
OnPropertyChanged();
}
}
What else do I need to do to get this to bind to the DataGrid?
<DataGridTextColumn Header="Name" Binding="{Binding Path=SelectedTenant.FullName, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
Edit:
I have set AutoGenerateColumns="True"
<DataGrid ItemsSource="{Binding MonthlyRecords}" AutoGenerateColumns="True">
<DataGridTextColumn Header="Name" Binding="{Binding ElementName=ComboBoxTenant, Path=DisplayMemberPath}"/>

Binding ObservableCollection to DataGrid

How do I bind a ObservableCollection<updateData> updateCollection to a DataGrid? I tried several solution but none seem to work as rows are added to the collection but don't show up on the grid. I tried to bind to the class only, then I can add rows but when I try to edit them i get the error 'EditItem' is not allowed for this view. The grid is the following
<DataGrid Name="dgv" Grid.ColumnSpan="7" AutoGenerateColumns="False" ItemsSource="{Binding updateCollection}" IsSynchronizedWithCurrentItem="True" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Hour" SelectedValueBinding="{Binding Active}" ItemsSource="{StaticResource hoursList}" DisplayMemberPath="Key" SelectedValuePath="Value"/>
<DataGridComboBoxColumn Header="Origin" SelectedValueBinding="{Binding Origin}" ItemsSource="{StaticResource originList}" DisplayMemberPath="Key" SelectedValuePath="Value"/>
<DataGridTextColumn Header="P" Binding="{Binding Path=Price}"/>
<DataGridTextColumn Header="Q" Binding="{Binding Path=Quantity}"/>
</DataGrid.Columns>
And the updateData class is the following:
public class updateData
{
public string Price { get; set; }
public string Quantity { get; set; }
public string Origin { get; set; }
public string Hour { get; set; }
}
What you did looks correct, but if you miss one single thing, the DataContext… nothing will work.
Here an example just for you:
This is your Model:
public class updateData
{
public string Price { get; set; }
public string Quantity { get; set; }
public string Origin { get; set; }
public string Hour { get; set; }
}
Note that, if you want to tell to your view that something has changed, you have to implement the INotifyPropertyChanged interface.
This is your ViewModel:
public class updateDataVm
{
public ObservableCollection<updateData> updateCollection { get; set; }
public updateDataVm()
{
updateCollection = new ObservableCollection<updateData>();
}
}
And finally here is your View (note that i have changed ItemsSource to ItemsSource="{Binding}"):
<Grid>
<DataGrid Name="dgv" Grid.ColumnSpan="7" AutoGenerateColumns="False" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Hour" SelectedValueBinding="{Binding Active}" ItemsSource="{StaticResource hoursList}" DisplayMemberPath="Key" SelectedValuePath="Value"/>
<DataGridComboBoxColumn Header="Origin" SelectedValueBinding="{Binding Origin}" ItemsSource="{StaticResource originList}" DisplayMemberPath="Key" SelectedValuePath="Value"/>
<DataGridTextColumn Header="P" Binding="{Binding Path=Price}"/>
<DataGridTextColumn Header="Q" Binding="{Binding Path=Quantity}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
In your window (or generally control):
public partial class MainWindow : Window
{
public updateDataVm collection;
public MainWindow()
{
InitializeComponent();
collection = new updateDataVm();
DataContext = collection;
}
}

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.

WPF DataGrid binding don't work caliburn micro

I can't understand why it does not work..
This is my class
public class Article : Screen
{
public string Code { get; set; }
public string Description{ get; set; }
public decimal Cost{ get; set; }
public decimal Price{ get; set; }
}
This is XAML code of DataGrid:
<DataGrid Height="211" HorizontalAlignment="Left"
Margin="12,31,0,0" VerticalAlignment="Top" Width="521"
AutoGenerateColumns="False" ItemsSource="{Binding List}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Code}" Header="Code" />
<DataGridTextColumn Binding="{Binding Path=Description}" Header="Description" />
<DataGridTextColumn Binding="{Binding Path=Cost}" Header="Cost" />
<DataGridTextColumn Binding="{Binding Path=Price}" Header="Price" />
</DataGrid.Columns>
</DataGrid>
<Button Content="Button" Height="39" HorizontalAlignment="Left"
Margin="223,262,0,0" VerticalAlignment="Top" Width="110"
x:Name="AllArticles"/>
And this is my viewmodel
[Export(typeof(IShell))]
public class ArtsViewModel : Screen
{
public List<Article> List = new List<Article>();
public void AllArticles()
{
Recover recover = new Recover(); //a model called Recover
List = recover.Import().Articles; //return a List of Article
NotifyOfPropertyChange("List");
}
}
WHY THE DATAGRID DON'T WORK ?
To enable the binding conventions in Caliburn.Micro, you normally use the x:Name property in your XAML. If you, instead of explicitly binding the List property to ItemsSource of your DataGrid, use the name convention like this:
<DataGrid x:Name="List" Height="211" HorizontalAlignment="Left"
Margin="12,31,0,0" VerticalAlignment="Top" Width="521"
AutoGenerateColumns="False">
I believe the subsequent bindings should work as desired.
Oh, and you also need to make List a property instead of a field:
public List<Article> List { get; private set; }
If you want to make sure that modifications to List are properly reflected in your data grid, you should also make your List property an IObservableCollection with a backing field:
private IObservableCollection<Article> _list;
public IObservableCollection<Article> List {
get { return _list; }
set {
_list = value;
NotifyOfPropertyChange(() => List);
}
}
a) Did you set the DataContext?
b) Try "Binding="{Binding Code}" (Without Path=), works fine for me.
For my UWP app I found that the x:Name convention didn't work and I need to use a bindable collection and bind directly to ItemsSource. I then used Path=PropertyName to specify the columns.
XAML:
<controls:DataGrid Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3" ItemsSource="{Binding RawCSVData}" AutoGenerateColumns="false">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Header="Time" Binding="{Binding Path=Time}"/>
<controls:DataGridTextColumn Header="Signal" Binding="{Binding Path=Signal}"/>
<controls:DataGridTextColumn Header="Response" Binding="{Binding Path=Response}"/>
</controls:DataGrid.Columns>
</controls:DataGrid>
CSVData.cs:
public class CSVData: Screen
{
public double Time { get; set; }
public double Signal { get; set; }
public double Response { get; set; }
}
ViewModel.cs snippet:
public BindableCollection<CSVData> RawCSVData { get; set; }
private void _bookeditpage_ConfirmBook(Book _book, bool _isnewone)
{
if (_isnewone)
{
Books.Add(_book);
}
else
{
//doesn't refresh datagrid
SelectedBook = _book;
//
//works fine :)
SelectedBook.BookName = _book.BookName;
SelectedBook.IsPrenum=_book.IsPrenum;
SelectedBook.Publisher=_book.Publisher;
//
}
}

WPF datagrid dropdown two-way binding

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" />

Resources