populate combobox inside the datagrid - c#

I have a DataGrid to insert data into Sql Server table. Inside of DataGrid I have ComboBox to pick the data from a codebook (this is the table from the same database) and insert this data into table I bound to DataGrid. This is the XAML:
<DataGrid Name="dgrStavke" AutoGenerateColumns="False" Height="160" Width="600" HorizontalAlignment="Left" Margin="5" Grid.Row="7" Grid.ColumnSpan="4">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Artikl ID">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="cmbArticle" Width="120" ></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Article Name" Width="150"></DataGridTextColumn>
<DataGridTemplateColumn Header="Usluga ID">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="cmbService" Width="120"></ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Service Name" Width="150"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
When I choose article id in combobox article, the very next DataGridTextColumn should display name of this article. The same is for service. It's possible to insert few articles and services and that's the reason I use Datagrid.
How can I do this?
Thanks.

I guess the below sample would help you to achieve your goal
In XAML below code used,
<Window.Resources>
<ObjectDataProvider x:Key="RecordValues" ObjectType="{x:Type local:RecordTemp}" MethodName="GetPersonList" >
</ObjectDataProvider>
</Window.Resources>
<Grid>
<Grid>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}"
Margin="10,10,73,155" Name="dataGrid1">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Artikl ID" Width="300"
SelectedValueBinding="{Binding SelectedPart, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="ArticleID"
ItemsSource="{Binding Source={StaticResource RecordValues}}" />
<DataGridTextColumn Header="Order Name" Binding="{Binding SelectedPart.ArticleName}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
In C# below code used,
public class RecordTemp : INotifyPropertyChanged
{
List<PartsList> _value = new List<PartsList>();
public RecordTemp()
{
_value.Add(new PartsList() { ArticleID = "1", ArticleName = "one - 1", ArticleQuantity = 20 });
_value.Add(new PartsList() { ArticleID = "2", ArticleName = "Two - 2", ArticleQuantity = 10 });
}
public List<PartsList> GetPersonList()
{
return _value;
}
private PartsList _SelectedPart;
public PartsList SelectedPart
{
get { return _SelectedPart; }
set
{
_SelectedPart = value;
OnPropertyChanged("SelectedPart");
}
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class PartsList
{
public string ArticleID { get; set; }
public double ArticleQuantity { get; set; }
public string ArticleName { get; set; }
}
In the MainWindow the below code could helps to binding the grid.
ObservableCollection<RecordTemp> RecordsTemp = new ObservableCollection<RecordTemp>();
RecordsTemp.Add(new RecordTemp());
RecordsTemp.Add(new RecordTemp());
dataGrid1.DataContext = RecordsTemp;
Please Comment If you have any clarification needed. I'll help you

((ComboBox)GRID.Columns[N].FindControl("cmbArticle")).COMBO-BOX-PROPERTY
Is this correct syntax? I don't know, I think you are looking for something like that.

Related

WPF DataGrid - How to automatically set EditMode on a newly added row?

I have a program which so far follows MVVM principles/rules with 0 code behind and I have a DataGrid where users can add, edit or delete rows which represent students.
A user can add a row to the list by clicking on a "+" button but in order to edit the row the user has to first click on the row he just added which isn't so user-friendly.
I have been trying to set the newly added row in EditMode for a good while but all my attempts either failed or worked but with some disturbing side effects on the rest of the program. I looked it up online and the solutions I found either look like overkill or also have a bad side effect.
I created a similar program with less code to make it easier to show the structure of my program and DataGrid:
Model
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string PhoneNumber { get; set; }
public string Address { get; set; }
public string Email { get; set; }
}
ViewModel
public class StudentsViewModel : INotifyPropertyChanged
{
public StudentsViewModel()
{
Students = new ObservableCollection<Student>();
}
private ObservableCollection<Student> students;
public ObservableCollection<Student> Students
{
get { return students; }
set
{
students = value;
NotifyPropertyChanged(nameof(Students));
}
}
private Student selectedStudent;
public Student SelectedStudent
{
get { return selectedStudent; }
set
{
selectedStudent = value;
NotifyPropertyChanged(nameof(SelectedStudent));
}
}
private ICommand addRow;
public ICommand AddRow
{
get
{
if(addRow == null)
{
addRow = new RelayCommand(
parameter => AddStudent(new Student()),
parameter => true
);
}
return addRow;
}
}
private ICommand removeCmd;
public ICommand RemoveCmd
{
get
{
removeCmd = new RelayCommand(
parameter => RemoveStudent(parameter as Student),
parameter => parameter != null
);
return removeCmd;
}
}
private void AddStudent(Student studentToAdd)
{
Students.Add(studentToAdd);
}
private void RemoveStudent(Student studentToRemove)
{
if (Students.Contains(studentToRemove))
{
Students.Remove(studentToRemove);
}
}
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
View
<Window x:Class="DataGridExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataGridExample"
mc:Ignorable="d"
Title="MainWindow"
Height="600"
Width="1000">
<Window.Resources>
<local:StudentsViewModel x:Key="StudentsVm"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource StudentsVm}}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<DockPanel LastChildFill="False"
Background="#FF2C58EC">
<Button Command="{Binding AddRow}"
Height="25"
Margin="5">
<Button.Template>
<ControlTemplate>
<Image Source="/Images/AddItem.png"/>
</ControlTemplate>
</Button.Template>
</Button>
<Button Command="{Binding RemoveCmd}"
CommandParameter="{Binding ElementName=StudentDataGrid, Path=SelectedItem}"
Height="25"
Margin="5">
<Button.Template>
<ControlTemplate>
<Image Source="/Images/DeleteItem.png"/>
</ControlTemplate>
</Button.Template>
</Button>
</DockPanel>
<DataGrid ItemsSource="{Binding Students}"
SelectedItem="{Binding Source={StaticResource StudentsVm}, Path=SelectedStudent, Mode=TwoWay}"
x:Name="StudentDataGrid"
ColumnWidth="*"
CanUserAddRows="False"
CanUserResizeRows="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
CanUserDeleteRows="False"
AutoGenerateColumns="False"
Grid.Row="1">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Header="First Name">
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding LastName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Header="Last Name">
</DataGridTextColumn>
<DataGridTemplateColumn Header="Date of Birth">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DateOfBirth, StringFormat={}{0:dd.MM.yyyy}, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding DateOfBirth, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
DisplayDate="{Binding DateOfBirth, Mode=OneWay, UpdateSourceTrigger=LostFocus}">
</DatePicker>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding PhoneNumber, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Header="Phone Number">
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Address, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Header="Address">
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Header="Email">
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
I would prefer the solution to be MVVM compatible but honestly at this point I would be satisfied as long as it doesn't cause other problems and doesn't require tons of frameworks or multiples files full of code.
I would prefer the result to look like this example but this is also acceptable as long as no mouse movement is involved after the "+" button click.
I found a way to achieve what you want but it doesn't seem very robust so use with care. First, you should hookup to the LoadingRow event of your DataGrid.
XAML
<DataGrid ...
LoadingRow="StudentDataGrid_LoadingRow"
...>
Code-behind
private async void StudentDataGrid_LoadingRow(object sender, DataGridRowEventArgs e) {
// Force asynchrony to run callback after UI has finished loading.
await Task.Yield();
// Mark the new row as selected
StudentDataGrid.SelectedItem = e.Row.Item;
StudentDataGrid.CurrentCell = new DataGridCellInfo(e.Row.Item, StudentDataGrid.Columns[0]);
// Enter edit mode
StudentDataGrid.BeginEdit();
}
By making the method async and by forcing it to execute asynchronously through the await Task.Yield() call, you let the UI finish the loading of the row before asking it to start an edit through the BeginEdit() call.
I'm writing this more as an experiment and I don't know if I would recommend this but I hope it helps while someone finds a better solution.

How to refresh a DataGrid that has been populated by a DataProvider every 1min

I have the following xaml, as you can see the DataGrids are being populated via DataProviders.
<Window x:Class="MobileDeviceAuthenticator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MobileDeviceAuthenticator"
Title="Device Authorization" Height="381" Width="879" AllowDrop="True">
<Window.Resources>
<!-- create an instance of our DataProvider class -->
<ObjectDataProvider x:Key="MobileManagerDataProvider" ObjectType="{x:Type local:MobileManagerDataProvider}"/>
<!-- define the method which is invoked to obtain our data -->
<ObjectDataProvider x:Key="MOBILE_MANAGER" ObjectInstance="{StaticResource MobileManagerDataProvider}" MethodName="GetDevices"/>
<!-- create an instance of our DataProvider class -->
<ObjectDataProvider x:Key="MobileRequestDataProvider" ObjectType="{x:Type local:MobileRequestDataProvider}"/>
<!-- define the method which is invoked to obtain our data -->
<ObjectDataProvider x:Key="MOBILE_REQUESTS" ObjectInstance="{StaticResource MobileRequestDataProvider}" MethodName="GetDevices"/>
</Window.Resources>
<Grid Name="GridContainer" >
<Grid>
<DataGrid Name="dgAuthorization"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
DataContext="{Binding Source={StaticResource MOBILE_MANAGER}}"
ItemsSource="{Binding}"
AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=DESCRIPTION}" Header="Description" />
<DataGridTextColumn Binding="{Binding Path=DEVICE_TYPE}" Header="Device Type" IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding Path=DEVICE_ID}" Header="Device ID" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
</Grid>
<Grid>
<DataGrid Name="dgRequest"
SelectionMode="Single"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
DataContext="{Binding Source={StaticResource MOBILE_REQUESTS}}"
ItemsSource="{Binding}"
AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=DESCRIPTION}" Header="Description" />
<DataGridTextColumn Binding="{Binding Path=DEVICE_TYPE}" Header="Device Type" IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding Path=DEVICE_ID}" Header="Device ID" IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding Path=REQUEST_DATE}" Header="Request Date" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>
Here is the DataProvider class code
public class MobileManagerDataProvider
{
private MobileManagerDataSetTableAdapters.MOBILE_MANAGERTableAdapter mmAdapter;
private MobileManagerDataSet mmDataSet;
public MobileManagerDataProvider()
{
mmDataSet = new MobileManagerDataSet();
mmAdapter = new MobileManagerDataSetTableAdapters.MOBILE_MANAGERTableAdapter();
mmAdapter.Fill(mmDataSet.MOBILE_MANAGER);
mmDataSet.MOBILE_MANAGER.MOBILE_MANAGERRowChanged += new MobileManagerDataSet.MOBILE_MANAGERRowChangeEventHandler(AuthenticationRowModified);
mmDataSet.MOBILE_MANAGER.MOBILE_MANAGERRowDeleted += new MobileManagerDataSet.MOBILE_MANAGERRowChangeEventHandler(AuthenticationRowModified);
}
public DataView GetDevices()
{
return mmDataSet.MOBILE_MANAGER.DefaultView;
}
void AuthenticationRowModified(object sender, MobileManagerDataSet.MOBILE_MANAGERRowChangeEvent e)
{
mmAdapter.Update(mmDataSet.MOBILE_MANAGER);
}
}
In the code-behind I'd like to setup a Timer ticking away every minute refreshing the DataGrids.
The Timer is easy enough, however to refresh the data is eluding me. I've tried some of the following statements.
ObjectDataProvider dataProvider = this.TryFindResource("MobileManagerDataProvider") as ObjectDataProvider;
dataProvider.Refresh();
dgAuthorization.Items.Refresh();
CollectionViewSource.GetDefaultView(dgAuthorization.ItemsSource).Refresh();
To no avail, how can I achieve this?
this is a complete working example, every time you select an item (not same item twice) it will add a new random value as a new line in the DataGrid
I've responded with an example binding new data to the DataGrid every time you select an item in it, you can expand that case, so each time your collection changes, it will reflect in your DataGrid automatically, so whenever your data changes you just pass to the GridData property they will be updated in the view
MainWindow.xaml
<Window x:Class="BindingDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingDataGrid"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<DataGrid ColumnWidth="*"
IsReadOnly="True"
AutoGenerateColumns="False"
SelectionMode="Single"
HorizontalContentAlignment="Center"
ItemsSource="{Binding GridData}"
SelectedItem="{Binding SelectedData, UpdateSourceTrigger=PropertyChanged}"
CanUserResizeRows="False"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridTextColumn Header="Column 01" Binding="{Binding Column1}" Width="*"/>
<DataGridTextColumn Header="Column 02" Binding="{Binding Column2}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs Nothing here, just remove unused references
using System.Windows;
namespace BindingDataGrid //`BindingDataGrid` is my namespace
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
all references you need in your MainViewModel
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
MainViewModel
public class MainViewModel: INotifyPropertyChanged
{
Random _rnd = new Random();
private MyGridData _selectedData;
private ObservableCollection<MyGridData> _gridData;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainViewModel()
{
var newData = new List<MyGridData>
{
new MyGridData { Column1 = "AAAAAA 01", Column2 = "aaaaaaaa 02" },
new MyGridData { Column1 = "BBBBBB 01", Column2 = "bbbbbbbb 02" },
new MyGridData { Column1 = "CCCCCC 01", Column2 = "cccccccc 02" },
};
GridData = new ObservableCollection<MyGridData>(newData);
}
public MyGridData SelectedData
{
get { return _selectedData; }
set
{
if (value == _selectedData) //if same item selected
return;
_selectedData = value;
OnPropertyChanged();
DoSomethingWhenValueChanged(); // Will add a new line
}
}
public ObservableCollection<MyGridData> GridData
{
get { return _gridData; }
set
{
if (value == _gridData)
return;
_gridData = value;
OnPropertyChanged();
}
}
private void DoSomethingWhenValueChanged()
{
var newData = GridData.ToList();
newData.Add(new MyGridData { Column1 = (_rnd.Next(100)).ToString(), Column2 = (_rnd.Next(1000)).ToString() }); //Add new random item
GridData = new ObservableCollection<MyGridData>(newData); //update your DataGrid
}
}
public class MyGridData
{
public string Column1 { get; set; }
public string Column2 { get; set; }
}

WPF Binding DataGrid

I am trying to bound some properties from my DataContext to my DataGrid without success...
The DataGridComboBox is empty and invisible before clicking on it, and I have two rows instead of one for the following source code.
The XAML
<DataGrid Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" ItemsSource="{Binding ClassRow}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Classe" x:Name="Class_ClassName" Width="100" SelectedItemBinding="{Binding ClassName, Mode=TwoWay}" ItemsSource="{Binding ClassList}"/>
<DataGridTemplateColumn Header="Niveau">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="LevelTxt" Width="50" TextChanged="LevelTxtTextChanged" Text="{Binding ClassLevel, TargetNullValue={x:Static sys:String.Empty}}"/>
<StackPanel Orientation="Vertical">
<Button x:Name="LevelUp" Content="+" Width="15" Height="15" Click="LevelUpClick" FontSize="10" VerticalContentAlignment="Top" />
<Button x:Name="LevelDown" Content="-" Width="15" Height="15" Click="LevelDownClick" FontSize="12" VerticalContentAlignment="Bottom"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The DataContext :
public class ClassRow
{
public String ClassName;
public int ClassLevel;
}
public class PJDataWindow : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected List<ClassRow> m_classRow;
protected List<String> m_classList;
public PJDataWindow()
{
m_classRow = new List<ClassRow>();
m_classList = new List<String>();
//Test
m_classList.Add("Classe1");
m_classRow.Add(new ClassRow { ClassName = "Classe1", ClassLevel = 2 });
OnPropertyChanged("ClassList");
OnPropertyChanged("ClassRow");
}
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public List<ClassRow> ClassRow
{
get
{
return m_classRow;
}
set
{
m_classRow = value;
OnPropertyChanged("ClassRow");
}
}
public List<String> ClassList { get => m_classList; set { m_classList = value; OnPropertyChanged("ClassList"); } }
}
I am new in WPF and my search has led me to nowhere...
Thank you a lot !
There are 3 problems (so far).
The easiest problem is the two rows instead of one. The extra row is the new item row. Turn this off like so...
<DataGrid Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" ItemsSource="{Binding ClassRow}" AutoGenerateColumns="False" Margin="40" CanUserAddRows="False">
The next problem is the empty text field. You can only bind to properties not fields. So to fix this change the fields in the row object to properties.
public class ClassRow
{
public String ClassName { get; set; }
public int ClassLevel { get; set; }
}
Finally the reason the combo box is empty is that the items source is not bound to the data context. The data grid column is outside the visual tree. So it cannot find the source. Fix it like this.
<FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>
<DataGrid Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" ItemsSource="{Binding ClassRow}" AutoGenerateColumns="False" Margin="40" CanUserAddRows="False" x:Name="dataGrid">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Classe" x:Name="Class_ClassName" Width="100" SelectedItemBinding="{Binding ClassName, Mode=TwoWay}" ItemsSource="{Binding Source={x:Reference dummyElement}, Path=DataContext.ClassList}"/>

WPF DataGrid TextBoxes retaining previous values when there are multiple validation errors

I have created a DataGrid with two columns that both use TextBoxs for editing the properties of a ViewModel. When both columns have validation errors, and the property values are changed from the ViewModel, entering edit mode in one of the cells retains the previously edited value.
Here is a short example:
View
<Window ...>
<Window.DataContext>
<ViewModels:MainPresenter />
</Window.DataContext>
<DockPanel>
<Button Command="{Binding ResetValuesCommand}"
Margin="5" DockPanel.Dock="Top">Reset Values</Button>
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
<DataGrid.Columns>
<DataGridTextColumn Header="Value 1"
Binding="{Binding Value1, ValidatesOnDataErrors=True}" />
<DataGridTextColumn Header="Value 2"
Binding="{Binding Value2, ValidatesOnDataErrors=True}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
ViewModel
public class MainPresenter : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<ItemPresenter> Items { get; }
= new ObservableCollection<ItemPresenter> {new ItemPresenter()};
public ICommand ResetValuesCommand => new ResetCommand(Items);
private class ResetCommand : ICommand
{
private readonly IEnumerable<ItemPresenter> _items;
public ResetCommand(IEnumerable<ItemPresenter> items) { _items = items; }
public void Execute(object parameter) => _items.ToList().ForEach(i => i.Reset());
public bool CanExecute(object parameter) => true;
public event EventHandler CanExecuteChanged { add { } remove { } }
}
}
public class ItemPresenter : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string Value1 { get; set; } = "A";
public string Value2 { get; set; } = "B";
public string this[string columnName] => "ERROR";
public string Error => "ERROR";
public void Reset()
{
Value1 = "A";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
Value2 = "B";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
}
}
Steps to Reproduce
When running the app, both columns are highlighted as being invalid.
Double-click on the cell in 'Value 1' column (currently having value "A") and change it, e.g. to "Z";
Press enter (or equivalent) to commit the change;
Press the 'Reset Values' button (causing the cell we have just edited to change to "A");
Double-click on the cell in 'Value 1' column again.
The cell in 'Value 1' column changes to edit mode and the value is shown as "Z" again.
One thing to note: this only occurs when other columns have validation errors. If this is the only column with a validation error, then the TextBox in edit mode will correctly show "A" when entering edit mode.
Partial Fix
Oddly, explicitly setting the Mode in the bindings to TwoWay (presumably the default, as that's the apparent behaviour) fixes the problem.
However, if I want some custom cell templates (and replace the DataGridTextColumns with DataGridTemplateColumnss) but still use a TextBox for editing:
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Value 1">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBlock Text="{Binding Value1, ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBox Text="{Binding Value1, ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value 2">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBlock Text="{Binding Value2, ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBox Text="{Binding Value2, ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I encounter the same problem but explicitly setting the binding modes to TwoWay does not fix it.
Am I doing something wrong somewhere, that I've overlooked? Alternatively, does anyone have a workaround for this?
I've found a workaround.
If I change to using INotifyDataErrorInfo (only available in .NET 4.5 and above), then it works as expected.
ViewModel
public class ItemPresenter : INotifyPropertyChanged, INotifyDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public string Value1 { get; set; } = "A";
public string Value2 { get; set; } = "B";
public IEnumerable GetErrors(string propertyName) => new[] { "ERROR" };
public bool HasErrors => true;
public void Reset()
{
Value1 = "A";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value1)));
Value2 = "B";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value2)));
}
}
View
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" Margin="5">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Value 1">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBlock Text="{Binding Value1}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBox Text="{Binding Value1}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value 2">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBlock Text="{Binding Value2}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type ViewModels:ItemPresenter}">
<TextBox Text="{Binding Value2}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

WPF: data won't display in my datagrid

I'm learning XAML with WPF and i'm trying to display some data in a grid, but i can't for the life of me get it to show. the fields appear, but they're empty without text. here's the XAML
<Window x:Class="MoodWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MoodWPF"
mc:Ignorable="d"
Height="810.573"
Width="1026.432"
MinWidth="600">
<Grid x:Name="mainGrid" Background="#333333">
<DataGrid Width="400" Height="400" x:Name="trackList" HorizontalAlignment="Center" VerticalAlignment="Center" Background="#FF404040" BorderBrush="{x:Null}" ColumnHeaderHeight="22" GridLinesVisibility="Vertical" RowBackground="#FF404040" AlternatingRowBackground="#FF333333" RowHeight="20" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="Track" Header="Track" Width="100" IsReadOnly="True">
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Artist" Width="100" IsReadOnly="True">
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Album" Width="100" IsReadOnly="True">
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Time" Width="100" IsReadOnly="True">
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
and the C# with which im trying to insert some random data
namespace MoodWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// create and add three lines of fake data to be displayed, here
trackList.Items.Add(new DataItem { Track = "a.1", Artist = "a.2", Album = "a.3", Time = "a.4" });
trackList.Items.Add(new DataItem { Track = "a.1", Artist = "a.2", Album = "a.3", Time = "a.4" });
trackList.Items.Add(new DataItem { Track = "a.1", Artist = "a.2", Album = "a.3", Time = "a.4" });
/*
Collection<DataItem> users = new Collection<DataItem>();
users.Add(new DataItem { Track = "firstname-1", Artist = "lastname-1" });
users.Add(new DataItem { Track = "firstname-2", Artist = "lastname-2" });
users.Add(new DataItem { Track = "firstname-3", Artist = "lastname-3" });
users.Add(new DataItem { Track = "firstname-4", Artist = "lastname-4" });
trackList.ItemsSource = users;
}
}
public class DataItem
{
public string Track { get; set; }
public string Artist { get; set; }
public string Album { get; set; }
public string Time { get; set; }
}
}
what am i doing wrong/not doing? keep in mind i'm a beginner with WPF and Xaml
There are few issues,
(i) You need to have a collection to bind to DataGrid, hence add the items to a List or ObservableCollection
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<DataItem> data = new List<DataItem>();
data.Add(new DataItem { Track = "a.1", Artist = "a.2", Album = "a.3", Time = "a.4" });
data.Add(new DataItem { Track = "a.1", Artist = "a.2", Album = "a.3", Time = "a.4" });
data.Add(new DataItem { Track = "a.1", Artist = "a.2", Album = "a.3", Time = "a.4" });
trackList.ItemsSource = data;
}
}
public class DataItem
{
public string Track { get; set; }
public string Artist { get; set; }
public string Album { get; set; }
public string Time { get; set; }
}
(ii)Set DataGrid AutoGenerateColumns="True"
If you set AutoGenerateColums= false,
XAML:
<DataGrid Width="400" Height="400" x:Name="trackList" HorizontalAlignment="Center" VerticalAlignment="Center" Background="#FF404040" BorderBrush="{x:Null}" ColumnHeaderHeight="22" GridLinesVisibility="Vertical" RowBackground="#FF404040" AlternatingRowBackground="#FF333333" RowHeight="20" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Track}" x:Name="Track" Header="Track" Width="100" IsReadOnly="True">
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Artist}" Header="Artist" Width="100" IsReadOnly="True">
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Album}" Header="Album" Width="100" IsReadOnly="True">
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Time}" Header="Time" Width="100" IsReadOnly="True">
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
When you need only text try:
<DataGrid.Columns>
<DataGridTextColumn x:Name="Track" Width="100" Binding="{Binding Track}" Header="Track"/>
...
or you need specific template:
<DataGridTemplateColumn Header="Artist" Width="100" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Artist}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
You must to bind your model (probably a List of specific object) to the DataGrid, and its attributes to its repective columns.
Kind of:
<DataGrid x:Name="TrackList" DataContext="{Binding Source=TrackList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Track" Binding="{Binding Track}"/>
<DataGridTextColumn Header="Artist" Binding="{Binding Artist}"/>
...........
...........
</DataGrid.Columns>
</DataGrid>

Categories