WPF Datagrid cell value edit - C# - c#

I have a simple datagrid with 2 columns that I populate using an observable collection. The requirement is to enable cell edit for one of the columns and then using a button, save the column data somewhere. This is how I have implemented it so far:
View Model:
public class PlanningResult : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public string ProducerName { get; set; }
public string leasename { get; set; }
}
Observable Collection:
ObservableCollection<PlanningResult> populatePatternData = new ObservableCollection<PlanningResult>();
public ObservableCollection<PlanningResult> PopulatePatternData
{
get { return populatePatternData; }
set
{
populatePatternData = value;
base.OnPropertyChanged("StringList");
}
}
XAML : I set the "IsReadOnly=False" for the property ProducerName , hence allowing user to update this value if required.
<DataGrid x:Name="PrintReport" ItemsSource="{Binding PopulatePatternData}" AutoGenerateColumns="False" FontFamily="Tahoma" FontSize="12" CanUserSortColumns="False"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" AlternatingRowBackground="Gainsboro" AlternationCount="1"
SelectionMode="Extended" SelectionUnit="Cell" >
<DataGrid.Columns>
<DataGridTextColumn Header="Pattern" Binding="{Binding ProducerName}" IsReadOnly="False" >
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Lease" Binding="{Binding leasename}" IsReadOnly="True" >
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
My question is what is the next step in terms of how to "get the updated values of the column (ProducerName)" and re-populate the observable collection?

Use a TwoWay mode Binding:
Binding="{Binding ProducerName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
This will update the specific item in the ObservableCollection, whenever the user changes the value of the cell.
Furthermore, use commanding in order to save the current state of your ObservableCollection. See this among many other answer and articles.

Related

C# WPF DataGrid setting multiple selected items programatically

I have a WPF window ViewAssignedStudentsWindow with the following constructor:
public ViewAssignedStudentsWindow(IEnumerable<StudentDTO> allStudents, MandatoryLessonDTO lesson)
{
InitializeComponent();
studentsGrid.ItemsSource = allStudents;
studentsGrid.SelectedItems.Add(allStudents.Where(x => lesson.StudentIds.Contains(x.Id)));
}
The StudentDTO has properties FirstName, LastName, Id and some others, which should not be important for this problem. StudentIds property in the MandatoryLessonDTO class is an IEnumerable<Guid> which holds some of the students' Ids. The xaml of ViewAssignedStudentWindow:
<DataGrid SelectionMode="Extended" IsReadOnly="true" HeadersVisibility="Column" ItemsSource="{Binding}" ColumnWidth="*" DockPanel.Dock="Top" Background="White" AutoGenerateColumns="false" x:Name="studentsGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="First name" Binding="{Binding FirstName}" />
<DataGridTextColumn Header="Last name" Binding="{Binding LastName}" />
</DataGrid.Columns>
</DataGrid>
The problem is that the grid gets filled with the students' data, however the SelectedItems doesn't seem to work - none of the items are selected. If I try to debug the code, the LINQ seems to yield correct results, however the SelectedItems stays empty. I am completely lost at this point why this isn't working, it seems like such a simple task. Any help would be much appreciated.
I would recommend a DataBinding to an Items property here.
your students class:
public class Students : INotifyPropertyChanged {
private bool _selected;
public bool Selected {
get { return _selected; }
set {
if (value == _selected) return;
_selected = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Add a RowStyle to your DG:
<DataGrid ... >
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding Selected}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
Selecting Rows from CodeBehind:
public ViewAssignedStudentsWindow(IEnumerable<StudentDTO> allStudents, MandatoryLessonDTO lesson)
{
InitializeComponent();
studentsGrid.ItemsSource = allStudents;
allStudents.ForEach(x => x.Selected = lesson.StudentIds.Contains(x.Id)));
}

Bind a DataGrid to an ObservableCollection<T> in WPF

How can I bind a Datagrid with two columns (DataGridTextColumn and DataGridComboBoxColumn) to an ObservableCollection<Team> Teams?
The DataGridTextColumn is bound correctly. But nothing is displayed in DataGridComboBoxColumn.
Code-behind
public class Team
{
public string Name { get; set; }
public List<string> Members { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Team> teams = new ObservableCollection<Team>
{
new Team()
{
Name = "A", Members = new List<string> {"John", "Jack"},
},
new Team()
{
Name = "B", Members = new List<string> {"Sarah", "Anna"},
}
};
public ObservableCollection<Team> Teams
{
get { return teams; }
set
{
teams = value;
RaisePropertyChanged("Teams");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<DataGrid ItemsSource="{Binding Path=Teams}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Team"
IsReadOnly="True"
Width="*"
Binding="{Binding Name}"/>
<DataGridComboBoxColumn Header="Members"
Width="*"
/>
</DataGrid.Columns>
</DataGrid>
Just use "DataGridTemplateColumn".
Dont forget to add a selectedItem Member.
<DataGridTemplateColumn Header="Members">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="wpfApplication1:Team">
<ComboBox ItemsSource="{Binding Members}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Your need to setup DataContext property of your control with instance of ViewModel. Like, in control constructor
this.DataContext = new ViewModel();
and fill DisplayMemberPath in your XAML
<DataGridTextColumn Header="Team" DisplayMemberPath="Name" ...
UPDATE
I was inccorrect. Because DataGridComboboxColumn does not inherit DataContext, to do what you want you have to do in following way:
<DataGrid ItemsSource="{Binding Path=Teams}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Team"
IsReadOnly="True"
Width="*"
Binding="{Binding Name}" />
<DataGridComboBoxColumn Header="Members"
Width="*"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=Members}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=Members}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>

DataGridComboBoxColumn doesn't display my collection

I just want to do a simple thing, I just want to show my list of versions into a combobox in a datagrid.
The column "Versions" displays "Collections"... and I don't know why!
Here the code into my xaml:
<DataGrid Name="DataGridTableau" Grid.Column="0" Grid.Row="0"
AutoGenerateColumns="False"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" CanUserAddRows="True"
ItemsSource="{Binding }"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Sofware" Width="*" IsReadOnly="True" Binding="{Binding Path=Software}">
</DataGridTextColumn>
<DataGridComboBoxColumn Header="Version" Width="*" IsReadOnly="True"
ItemsSource="{Binding Path=Versions}">
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
And into xaml.cs :
public ObservableCollection<ElementTableau> Elements;
public VueEtapeSelection()
{
InitializeComponent();
Elements = new ObservableCollection<ElementTableau>();
List<string> versions = new List<string>();
versions.Add("3.0");
versions.Add("3.1");
Elements.Add(new ElementTableau("Excel", versions));
this.DataGridTableauEKs.DataContext = Elements;
}
public class ElementTableau
{
private string sofware;
public string Software
{
get { return software; }
set { software = value; }
}
private List<string> versions;
public List<string> Versions
{
get { return versions; }
set { versions = value; }
}
public ElementTableau(string software, List<string> versions)
{
this.software = software;
this.versions=versions
}
}
Thanks for your help!
Try setting the ItemsSource using a style:
<DataGrid.Columns>
<DataGridTextColumn Header="Sofware" Width="*" IsReadOnly="True" Binding="{Binding Path=Software}" />
<DataGridComboBoxColumn>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=Versions}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=Versions}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
Binding the ItemsSource property of the column doesn't work, because it is evaluated in the context of the column itself which has no parent FrameworkElement. Using the style instead sets the ItemsSource as soon as the ComboBox is being rendered. The ComboBox is integrated in the logical tree and its DataContext can be evaluated, that's why it works.
Edit:
To bind your selected items, add some setters like this:
<Setter Property="SelectedItem" Value="{Binding Path=SelectedVersion}" />

Refresh a single item in a WPF ItemCollection

I have a DataGrid that is bound to some XML data.
When I make changes in the XML data, the DataGrid does not refresh to reflect those changes.
My "simple" way of fixing this is to call MyDataGrid.Items.Refresh() every time I make a change.
However, this is laggy and seems pretty inefficient.
How can I refresh just a single row, rather than the entire data grid? I have easy access to the DataGridRow as well as the XmlElement that is changed, but I just don't know what function to call.
Been stuck on this problem for 3-4 hours now and have tried dozens of solutions, but just can't get it to work.
Below is relevant code.
A) Defining the style.
<!-- Field Value Style -->
<local:FieldValueConverter x:Key="FieldValueConverter"/>
<local:Node x:Key="Node"/>
<Style x:Key="fieldValueStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding ., Converter={StaticResource FieldValueConverter}}"/>
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
<Setter Property="Focusable" Value="False"/>
</Style>
B) Defining the DataGrid
<DataGrid x:Name="FieldPanelDataGrid" DockPanel.Dock="Left"
AutoGenerateColumns="False"
DataContext="{Binding ElementName=ObjectPanelListBox, Path=SelectedItem}"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True"
IsReadOnly="True"
CanUserResizeRows="False"
CanUserResizeColumns="True"
KeyboardNavigation.IsTabStop="False"
Visibility="Visible"
SelectionMode="Single">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<EventSetter Event="MouseDoubleClick" Handler="FieldCell_MouseDoubleClick"/>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="FieldCell_PreviewMouseLeftButonDown"></EventSetter>
<EventSetter Event="PreviewKeyDown" Handler="FieldCell_PreviewKeyDown"></EventSetter>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn x:Name="FieldName" Header="Name" CanUserSort="False" ElementStyle="{StaticResource fieldNameStyle}"/>
<DataGridTextColumn x:Name="FieldValue" Header="Value" Width="*" ElementStyle="{StaticResource fieldValueStyle}"/>
</DataGrid.Columns>
</DataGrid>
I suggest to use an ObservableCollection as ItemSource and the entries in the ObservableCollection have to implement INotifyPropertyChanged. Then you have the benefit if the rows change, the ObservableCollection will tell that your UI and it will update.
Example:
Your entry class:
public class MyXmlObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string fieldName;
public string FieldName
{
get { return fieldName; }
set
{
fieldName = value;
NotifyPropertyChanged("FieldName");
}
}
NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Your code for the UserControl (ViewModel, Controller or Code behind):
public ObservableCollection<MyXmlObject> MyCollection { get; set; }
And as I mentioned in your xaml you simply bind the collection to the ItemsSource
<DataGrid ItemsSource="{Binding MyCollection}" .../>
Now only the items beeing changed get updated.

Edit Datagrid row background based on object value

I have the following class:
public class Order
{
public int Id { get; set; }
public string OrdName { get; set; }
public int Quant { get; set; }
public int Supplied { get; set; }
}
and this DataGrid:
<DataGrid x:Name="dgOrders" Margin="5" CanUserAddRows="False" CanUserDeleteRows="False"
SelectionMode="Extended" ItemsSource="{Binding}"
SelectionUnit="FullRow" VerticalScrollBarVisibility="Auto"
Height="300" Width="700" HorizontalAlignment="Left" AutoGenerateColumns="False" Grid.Row="2">
<DataGrid.Columns>
<DataGridTextColumn Header="Order Name" Binding="{Binding OrdName}" IsReadOnly="True"/>
<DataGridTextColumn Header="Quantity" Binding="{Binding Quant}" IsReadOnly="True"/>
<DataGridTextColumn Header="Supplied" Binding="{Binding Supplied}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
What I want is that when the Quantity and Supplied properties are equal the row background color will change.
I tried it with an Event Trigger and a Converter but with no luck (possibly I didn't implement them in the xaml correctly).
also trying to do this from the code behind didn't work (tried to get the row instance like this suggests but I keep getting null for the row).
A Binding cannot be set on the Value property of a DataTrigger. Therefore, you'll need to add an extra property into your data type class:
public bool AreQuantityAndSuppliedEqual
{
return Quantity == Supplied;
}
Then, with this property, you can use the following Style:
<Style x:Key="EqualRowStyle" TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding AreQuantityAndSuppliedEqual}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
It would be used like so:
<DataGrid ItemsSource="{Binding YourItems}"
RowStyle="{StaticResource EqualRowStyle}" ... />

Categories