WPF DataGridTextColumn direct edit - wpf

i have very simple DataGridTextColumn which should be modified on doubleclick event.
question is what should be added to avoid exception System.InvalidOperationException: ''EditItem' is not allowed for this view.'
<DataGrid x:Name="DG" ItemsSource="{Binding}" GridLinesVisibility="None" Grid.Column="3" Grid.Row="2">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding VariantSet, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="60" />
</DataGrid.Columns>
</DataGrid>
simple class:
Public Class CName
Public Property Name As String = "not editable name"
End Class
on load simply added to datagrid
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Me.DG.Items.Add(New CName)
End Sub
when declared through template as following, there is no difference, same error
<DataGridTemplateColumn Header="Name" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
even when Implements ComponentModel.INotifyPropertyChanged is added to the CName, no difference

You've not shown us enough to tell what you're doing wrong.
Here's a working window with datagrid.
The code is c# but you can run it through a converter to vb if you particularly want vb. I think it's a bad idea for a beginner to choose vb nowadays. Almost nobody publishes samples using vb.
In my mainwindow:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding People}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FirstName}" Header="First Name"/>
<DataGridTextColumn Binding="{Binding LastName}" Header="SurName"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
That viewmodel:
public class MainWindowViewModel : BaseViewModel
{
private ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; }
}
public MainWindowViewModel()
{
People.Add(new Person { FirstName = "Chesney", LastName = "Brown" });
People.Add(new Person { FirstName = "Gary", LastName = "Windass" });
People.Add(new Person { FirstName = "Liz", LastName = "McDonald" });
People.Add(new Person { FirstName = "Carla", LastName = "Connor" });
}
}
Person just has those two properties first and last name:
public class Person : BaseViewModel
{
private string firstName;
public string FirstName
{
get { return firstName; }
set { firstName = value; RaisePropertyChanged(); }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { lastName = value; RaisePropertyChanged(); }
}
That inherits from BaseViewmodel which just implements inotifypropertychanged.
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here I am editing a row

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

DataGrid - Binding to a list of lists with MVVM

I have a 'Person' class that can have a list of Items.
Person.cs
public class Person : ObservableObject
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChangedEvent("Id");
}
}
...
Private IList<Item> _items;
public IList<Item> Items
{
get { return _items; }
set
{
_items = value;
RaisePropertyChangedEvent("Items");
}
}
}
Item.cs
public class Item : ObservableObject
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChangedEvent("Id");
}
}
...
}
I am trying to display this in a tabbed data grid. First Tab is "Person" and the second tab is "Items".
.XAML
<Window.DataContext>
<viewModels:PersonViewModel />
</Window.DataContext>
<TabControl>
<TabItem Header="Person">
<DataGrid ItemsSource="{Binding Person}" Width="1700" Height="800" AutoGenerateColumns="False" CanUserAddRows="false"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" RowHeight="20">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True"/>
...
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Items">
<DataGrid ItemsSource="{Binding Person.Items}" Width="1700" Height="800" AutoGenerateColumns="False" CanUserAddRows="false"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" RowHeight="20">
<DataGrid.Columns>
<DataGridTextColumn Header="Person Id" Binding="{Binding Person.Id}" IsReadOnly="True"/>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True"/>
...
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
View Model.cs
private ObservableCollection<Person> _persons = new ObservableCollection<Person>();
public ObservableCollection<Person> Person
{
get { return _persons; }
set
{
_persons = value;
RaisePropertyChangedEvent("Person");
}
}
This is as far as I have gotten. I'm assuming I need to somehow loop through the Person objects and then do Person.Items but am not sure.
I am able to display my "Person" object perfectly fine, it is just the "Items" tab I am having issues with. I get the error "cannot resolve property "Items" in datacontext of type ObservableCollection"
Thank you for any help
Create a view model class that keeps track of the currently selected Person as well as all people to be displayed in the first DataGrid:
public class ViewModel : ObservableObject
{
public ViewModel()
{
Persons = new ObservableCollection<Person>();
//populate your collection here...
Persons.Add(new Person() { });
//...
}
private Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
RaisePropertyChangedEvent("SelectedPerson");
}
}
public ObservableCollection<Person> Persons { get; private set; }
}
Set the DataContext of the TabControl, or its parent window, to an instance of this view model class:
tc.DataContext = new ViewModel();
And bind to the view model properties:
<TabControl x:Name="tc">
<TabItem Header="Person">
<DataGrid ItemsSource="{Binding Persons}"
SelectedItem="{Binding SelectedPerson}"
Width="1700" Height="800" AutoGenerateColumns="False" CanUserAddRows="false"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" RowHeight="20">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Items">
<DataGrid ItemsSource="{Binding SelectedPerson.Items}" Width="1700" Height="800" AutoGenerateColumns="False" CanUserAddRows="false"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" RowHeight="20">
<DataGrid.Columns>
<DataGridTextColumn Header="Person Id" Binding="{Binding Person.Id}" IsReadOnly="True"/>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>

How to resolve binding path error on grid view SelectedItem property?

I've set up a DataGrid which is bound to an Observable collection of CustomerModel objects. Also I set up properties for each of the fields within that model and a SelectedCustomer property of type MainViewModel in the VM.
But when I select one of the rows from the DataGrid in order to populate the textboxes below, I get a path error on the field properties, FirstName etc:
System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedCustomer' property not found on 'object' ''DataGrid' (Name='customersgrid')'. BindingExpression:Path=SelectedCustomer.FirstName; DataItem='DataGrid' (Name='customersgrid'); target element is 'TextBox' (Name='fNameTbx'); target property is 'Text' (type 'String')
System.Windows.Data Error: 23 : Cannot convert 'MongoDBApp.Models.CustomerModel' from type 'CustomerModel' to type 'MongoDBApp.ViewModels.MainViewModel' for 'en-US' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: TypeConverter cannot convert from MongoDBApp.Models.CustomerModel.
In order to debug the issue, I checked my data context of the View, which is set to the VM:
private MainViewModel ViewModel { get; set; }
private static ICustomerDataService customerDataService = new CustomerDataService(CustomerRepository.Instance);
public MainView()
{
InitializeComponent();
ViewModel = new MainViewModel(customerDataService);
this.DataContext = ViewModel;
}
I also checked that the public property names matched the binding names on the UI, which they do. I do know that the second error hints that it cannot convert between the DataGrid binding source of type CustomerModel and the SelectedItem property which is of type MainViewModel.
Anyone have an idea how I can debug this further?
An example of the UI XAML and it's binding paths:
<Grid>
<DataGrid x:Name="customersgrid" Grid.RowSpan="3" Grid.Column="1" Grid.ColumnSpan="3" AutoGenerateColumns="False"
ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="ID" />
<DataGridTextColumn Binding="{Binding FirstName}" Header="First Name" />
<DataGridTextColumn Binding="{Binding LastName}" Header="Last Name" />
<DataGridTextColumn Binding="{Binding Email}" Header="Email" />
</DataGrid.Columns>
</DataGrid>
<Label Grid.Row="4" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Top" Content="First Name:" />
<TextBox x:Name="fNameTbx" Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="2" Width="120" Height="23"
HorizontalAlignment="Left" VerticalAlignment="Top" TextWrapping="Wrap"
Text="{Binding SelectedCustomer.FirstName, ElementName=customersgrid}" />
</Grid>
And a short version of the MainViewModel:
namespace MongoDBApp.ViewModels
{
class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ICustomerDataService _customerDataService;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public MainViewModel(ICustomerDataService customerDataService)
{
this._customerDataService = customerDataService;
QueryDataFromPersistence();
}
#region Properties
private MainViewModel selectedCustomer;
public MainViewModel SelectedCustomer
{
get { return selectedCustomer; }
set
{
selectedCustomer = value;
RaisePropertyChanged("SelectedCustomer");
}
}
private ObservableCollection<CustomerModel> customers;
public ObservableCollection<CustomerModel> Customers
{
get { return customers; }
set
{
customers = value;
RaisePropertyChanged("Customers");
}
}
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
firstName = value;
RaisePropertyChanged("FirstName");
}
}
#endregion
private void QueryDataFromPersistence()
{
Customers = _customerDataService.GetAllCustomers().ToObservableCollection();
}
}
}
The error is here:
private MainViewModel selectedCustomer;
public MainViewModel SelectedCustomer
{
get
{
return selectedCustomer;
}
set
{
selectedCustomer = value;
RaisePropertyChanged("SelectedCustomer");
}
}
Change the type of selectedCustomer to CustomerModel instead of MainViewModel

DataGrid Silverlight Column Data binding

Let's say I have a Person Class like
Public class Person
{
public string firstName {set;get;}
public string lastName {set;get;}
}
in my ViewMode class I have an observableCollection of Person
private ObservableCollection<Person> _people;
public ObservableCollection<Person> People
{
get { return _people; }
set
{
_people= value;
FirePropertyChanged("People");
}
}
in my view I have a datagrid that ItemsSource is binded to People. I have a combobox in my view that its SelectedValue is binded to SelectedFieldValue and I want to add a row to my gridview with the value selected in this Combobox
private string _selectedFieldValue;
public string SelectedFieldValue
{
get { return _selectedFieldValue; }
set
{
_selectedFieldValue = value;
FirePropertyChanged("SelectedFieldValue");
People.Add(New Person{firstName = value});
}
}
the row is added to the Grid, but I don't see the value of the FirstName added. I tried several way to bind the column FirstName but couldn't get it working correctly.What's the correct way to bind my DataGridTextColumn in grid to firstName, lastName properties of the person class.
check out this..
<sdk:DataGrid x:Name="dataGrid4"
Height="160" ItemsSource="{Binding People}" AutoGenerateColumns="False" >
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn
Header="First Name"
Width="SizeToHeader"
Binding="{Binding firstName}"
FontSize="20" />
<sdk:DataGridTextColumn
Header="Last Name"
Width="SizeToCells"
Binding="{Binding lastName}"
FontSize="20" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>

Binding to a list within a collection WPF/VB

I was wondering if its possible to bind a datagrid column to a list (Of T) thats stored within an observable collection!
Here's my current code:
Dim _BindWithThis As New List(Of BindWithThis)
Me.DataContext = _BindWithThis
For i = 0 To 3
Dim NewList As New List(Of Double) From {i + 0.25, i + 0.5, i + 0.75}
_BindWithThis.Add(New BindWithThis() With _
{.InternalNum = i, .DataValue = NewList})
Next
DataGrid1.ItemsSource = _BindWithThis
Dim NC As New DataGridTextColumn
NC.Header = "New Column"
NC.Width = 85
Dim b As New Binding
b.Path = New PropertyPath("DataValue")
NC.Binding = b
DataGrid1.Columns.Add(NC)
This currently displays four rows of "(Collection)". Is it possible to step into one of these "Collection" rows and display the data? I know that this is possible to do with a list box by binding with a specific element in the collection:
ListBox1.ItemsSource = _BindWithThis.Item(0).DataValue
I just can't work out how to do this with a datagrid...
Thanks for any help!
James
Here's my example as promised. This uses the DataGrid.RowDetailsTemplate to allow you to expand/collapse your list of data. Apologies that it's C# and not VB.NET.
Xaml:
<Page.DataContext>
<Samples:DataGridRowDetails2ViewModel/>
</Page.DataContext>
<Grid>
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Items}"
AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" />
<DataGridTemplateColumn Header="Show details">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton Content="Show details"
IsChecked="{Binding IsChecked}"
Checked="ToggleButtonChecked" Unchecked="ToggleButtonChecked"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate DataType="{x:Type Samples:DataItemWithDetails}">
<ItemsControl ItemsSource="{Binding Doubles}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
C#
public partial class DataGridRowDetails2
{
public DataGridRowDetails2()
{
InitializeComponent();
}
private void ToggleButtonChecked(object sender, RoutedEventArgs e)
{
var button = (ToggleButton)sender;
DataGridRow dataGridRow =
(DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(button.DataContext);
dataGridRow.DetailsVisibility =
(button.IsChecked??false) ? Visibility.Visible : Visibility.Collapsed;
}
}
You would of course use ObservableCollection<> and implement INotifyPropertyChanged for real code.
public class DataGridRowDetails2ViewModel
{
public DataGridRowDetails2ViewModel()
{
Items = new List<DataItemWithDetails>
{
new DataItemWithDetails{ Name = "Item 1"},
new DataItemWithDetails{ Name = "Item 2"},
new DataItemWithDetails{ Name = "Item 3"},
new DataItemWithDetails{ Name = "Item 4"},
};
}
public IList<DataItemWithDetails> Items { get; set; }
public bool IsChecked { get; set; }
}
public class DataItemWithDetails
{
public DataItemWithDetails()
{
Doubles = new List<double> {1, 2, 3, 4};
}
public string Name { get; set; }
public IList<double> Doubles { get; set; }
}
You need to set AutoGenerateColumns="False" and define the columns you want yourself
Here's a quick example which will display your collection in a ListBox instead instead of the default TextBlock
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding YourCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Column Header">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding MySubCollection}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Resources