Edit Cell in DataGrid (WPF) - c#

I am new in WPF. I used to work in Winforms.
In Winforms I had the DataGridView that allows me to change, when I want a cell value.
Simply using:
dataGridView[columnIndex, rowIndex].Value = "New Value";
It works.
How can I accomplish this using DataGrid from WPF?
I was looking thorught stack over flow and could figure out an easy way to do this.
Thank you

Ok the simplest way to handle DataGrid is by binding to an ItemSource.
The example below shows how to bind your list and how changes upadte the DataGrid.
public partial class MainWindow : Window
{
private ObservableCollection<ConnectionItem> _connectionitems = new ObservableCollection<ConnectionItem>();
public MainWindow()
{
InitializeComponent();
ConnectionItems.Add(new ConnectionItem { Name = "Item1", Ping = "150ms" });
ConnectionItems.Add(new ConnectionItem { Name = "Item2", Ping = "122ms" });
}
public ObservableCollection<ConnectionItem> ConnectionItems
{
get { return _connectionitems; }
set { _connectionitems = value; }
}
private void button1_Click(object sender, RoutedEventArgs e)
{
// to change a value jus find the item you want in the list and change it
// because your ConnectionItem class implements INotifyPropertyChanged
// ite will automaticly update the dataGrid
// Example
ConnectionItems[0].Ping = "new ping :)";
}
}
public class ConnectionItem : INotifyPropertyChanged
{
private string _name;
private string _ping;
public string Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged("Name"); }
}
public string Ping
{
get { return _ping; }
set { _ping = value; NotifyPropertyChanged("Ping"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="property">The info.</param>
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Xaml:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
xmlns:properties="clr-namespace:WpfApplication4.Properties"
Title="MainWindow" Height="300" Width="400" Name="UI" >
<Grid>
<DataGrid Name="dataGridView" ItemsSource="{Binding ElementName=UI,Path=ConnectionItems}" Margin="0,0,0,40" />
<Button Content="Change" Height="23" HorizontalAlignment="Left" Margin="5,0,0,12" Name="button1" VerticalAlignment="Bottom" Width="75" Click="button1_Click" />
</Grid>
</Window>
i added a button to show how the data updates when you change something in your list, The class ConnectionItem is where you will store all your info for the datagrid.
Hope this helps

Related

Revalidate row of DataGrid after source/binding has been updated

It seems to me, that DataGrid only applies a ValidationRule defined as row validation rules inside
<DataGrid.RowValidationRules>
...
</DataGrid.RowValidationRules>
if the user makes an input. My view model implements INotifyDataErrorInfo and because of some asynchrounus background action the value of a cell might get invalid. Validation on cell level works perfectly with ValidatesOnNotifyDataErrors = true and my custom validation styles. However, my row validation rule that checks the cells for potentially invalid data will only update (and then mark the row invalid) if I manually make an arbitrary input (i.e. I start editing a cell and press Enter). Is there a possibility to manually trigger the row validation for a single/specific row? Is this behavior the intended behavior? Isn't the data grid supposed to revalidate in case a cell gets invalid?
This really drives me insance. I tried the same approach with IDataErrorInfo to verify that DataGrid has no bug. Below is a very simple WPF application. One can verify that the row validation will only trigger when the user changes the highlighted cell. Is there no way to trigger a row validation on my own? The only "solution" would to reduce validation on the cell level. For that purpose I created a dummy/read-only object property that aggregates all errors of the row. I use INotifyDataErrorInfo and return all errors if GetErrors is called with the dummy properties name. However, this can't be the solution! The whole WPF builds around Validation. VisualStateManager relies on internal validation, ...
Person.cs
class Person : IDataErrorInfo, INotifyPropertyChanged
{
private string _name;
public string this[string columnName]
{
get
{
string error = null;
switch(columnName)
{
case nameof(Name):
if (string.IsNullOrWhiteSpace(Name))
{
error = "Name is empty";
}
break;
}
return error;
}
}
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
public string Error
{
get { return null; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if(propertyName != null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} else
{
throw new ArgumentNullException();
}
}
}
PersonsViewModel.cs
class PersonsViewModel
{
private ObservableCollection<Person> _personsList;
public ObservableCollection<Person> PersonsList
{
get { return _personsList ?? (_personsList = new ObservableCollection<Person>()); }
}
public PersonsViewModel()
{
PersonsList.Add(new Person()
{
Name = "Bob"
});
PersonsList.Add(new Person()
{
Name = "Alice"
});
PersonsList.Add(new Person()
{
Name = "Max"
});
}
}
MainWindow.xaml
<Window x:Class="DataGridTesting.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:DataGridTesting"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding PersonsList, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" AutoGenerateColumns="False">
<DataGrid.RowValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="True" ValidationStep="CommittedValue"/>
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridTextColumn Header="Person" Binding="{Binding Path=Name, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True}"/>
</DataGrid.Columns>
</DataGrid>
<Button x:Name="MakeInvalidButton" Click="MakeInvalidButton_Click" Content="Make me invalid"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private PersonsViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new PersonsViewModel();
this.DataContext = viewModel;
}
private void MakeInvalidButton_Click(object sender, RoutedEventArgs e)
{
var alice = viewModel.PersonsList.Where(p => p.Name.StartsWith("Alice")).First();
if (alice != null)
{
alice.Name = string.Empty;
}
}
}

WPF Binding 2 Columns to ListView

XAML:
<ListBox x:Name="MyTitleBox" ItemsSource="{Binding}"/>
<ListBox x:Name="MyInfoBox" ItemsSource="{Binding}"/>
In C#:
MyTitleBox.ItemsSource = new List<string>(new string[] {"ID:", "Info:", "More Info:"});
MyInfoBox.ItemsSource = new ObservableCollection<string>(MyMainInfo.ToString().Split(',').ToList<string>());
I currently have 2 list boxes next to each other because I need to handle their ItemsSource programmatically.
I know there must be a better way to merge the two. Essentially the list box "on the left" is the titles like ID: and the list box "on the right" is the information.
I thought I could do something like MyTitleBox.Columns.Add like I've seen but it won't let me do .Columns. I'm using .NET 4.
Here is an example with a more MVVM approach:
Domain (The type of items you want to put in your list):
public class Movie : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
RaisePropertyChanged("Title");
}
}
}
private string _info;
public string Info
{
get { return _info; }
set
{
if (_info != value)
{
_info = value;
RaisePropertyChanged("Info");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Movie> _movies;
/// <summary>
/// Collection of movies.
/// </summary>
public ObservableCollection<Movie> Movies
{
get { return _movies; }
set
{
if (_movies != value)
{
_movies = value;
RaisePropertyChanged("Movies");
}
}
}
/// <summary>
/// Constructor
/// </summary>
public MyViewModel()
{
Movies = new ObservableCollection<Movie>();
Movies.Add(new Movie() { Title = "Gravity", Info = "Gravity is about..." });
Movies.Add(new Movie() { Title = "Avatar", Info = "Avatar is about..." });
}
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML:
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Movies}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Title}" /><Run Text=" - " /><Run Text="{Binding Info}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1" Content="Click To Change Info" Margin="5" Click="Button_Click" />
</Grid>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MyViewModel ViewModel { get; private set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MyViewModel();
DataContext = ViewModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Movie movie = ViewModel.Movies.FirstOrDefault();
if (movie != null)
{
movie.Info = "This is the new information";
}
}
}
Implementing INotifyPropertyChanged allows the code to notify the UI when something changes.
If you test this code out you will see that clicking the button updates the info for the first movie, and this changed is immediately reflected in the UI. (Normally you would use a convention like Commands for handling the button click, but for simplicity I did it this way)
Let me know if you have any questions.

WPF usercontrol binding to object don't work

I have a problem with binding my usercontrol to object in mainwindow. I don't know what is wrong.
I created custom control MyUserControl which has editable textbox
MyUsrControl.xaml
<UserControl x:Class="UserControlToObject.MyUsrControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="70" d:DesignWidth="200">
<StackPanel Orientation="Horizontal">
<Label Grid.Row="0" Grid.Column="0" Margin="5">Name</Label>
<TextBox Grid.Row="0" Grid.Column="1" Margin="5" Name="tbxName"></TextBox>
</StackPanel>
</UserControl>
Next I defined DP to allow modify this textbox outside countrol
MyUsrControl.xaml.cs
namespace UserControlToObject
{
public partial class MyUsrControl : UserControl
{
public static readonly DependencyProperty EmpNameProperty = DependencyProperty.Register("EmpNameProperty", typeof(string), typeof(MyUsrControl),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, EmpNamePropertyChanged));
public string EmpName
{
get
{
return (string)GetValue(EmpNameProperty);
}
set
{
SetValue(EmpNameProperty, value);
}
}
public MyUsrControl()
{
InitializeComponent();
}
static void EmpNamePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyUsrControl x = (MyUsrControl)sender;
x.tbxName.Text = (string)e.NewValue;
}
}
}
Next, I defined Employee class - properties of object of this class will be displayed on user control
namespace UserControlToObject
{
/// <summary>
/// Employee class
/// </summary>
class Employee : INotifyPropertyChanged
{
string m_name, m_surname;
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Employee name property
/// </summary>
public string Name
{
get { return m_name; }
set
{
m_name = value;
OnPropertyChanged("Name");
}
}
/// <summary>
/// Employee surname property
/// </summary>
public string Surname
{
get { return m_surname; }
set
{
m_surname = value;
OnPropertyChanged("Surname");
}
}
public Employee()
{
m_name = "unknown name";
m_surname = "unknown surname";
}
public Employee(string name, string surname)
{
m_name = name;
m_surname = surname;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
And finally MainWindow.xaml
<Window x:Name="myApp" x:Class="UserControlToObject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:UserControlToObject"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyUsrControl x:Name="ucEmp" EmpName="{Binding Name}"></local:MyUsrControl>
<Label Content="{Binding ElementName=ucEmp, Path=EmpName}"></Label>
</StackPanel>
</Window
>
MainWindow.xaml.cs
namespace UserControlToObject
{
public partial class MainWindow : Window
{
Employee anEmployee;
public MainWindow()
{
InitializeComponent();
anEmployee = new Employee("John", "Wayne");
this.DataContext = anEmployee;
}
}
}
This line don't work (error saying that I can set binding only on DependencyProperty... of DependencyObject):
<local:MyUsrControl x:Name="ucEmp" EmpName="{Binding Name}"></local:MyUsrControl>
Those settings below works, so I think that's problem with my Employee class (sth is missing ?)
<local:MyUsrControl x:Name="ucEmp" EmpName="John"></local:MyUsrControl> --> set EmpName ok
<Label Content="{Binding ElementName=ucEmp, Path=EmpName}"></Label> --> get EmpName ok
I've no idea whot is wrong, so will be very greatfull for help
I did the same but binding TextBox instead of user control and was no problem.
TextBox.Text is dependency property same as my Employee.EmpName
When registering the dependency property you need to use the name that you want to use in XAML. In your case, this is EmpName and not EmpNameProperty:
public static readonly DependencyProperty EmpNameProperty = DependencyProperty.
Register(nameof(EmpName), typeof(string), typeof(MyUsrControl), new
FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.
BindsTwoWayByDefault, EmpNamePropertyChanged));
"EmpNameProperty" in your property registeration should be ""EmpName"" w.
Thanks

How do you perform Binding with a DataGridView in WPF?

I want to bind a datagrid view in a user control that is docking to a main WPF form. However everytime I try to bind the data it must pre exist and won't update. Is there a way to perform this in the XAML directly to know when an event is triggered to update the datagridview rather than do it in the code behind?
Partial code of XAML:
xmlns:c="clr-namespace:TestWPFMain"
<UserControl.Resources>
<c:GridData x:Key="dataforGrid"/>
</UserControl.Resources>
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding Source={StaticResource dataforGrid}, Path=Results, Mode=TwoWay}" />
</Grid>
Code Behind for UserControl above:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
//datagridMain.ItemsSource = gd.Results;
-- This code above will work if I uncomment but I want it to be bound
directly and was curious as I thought the mode of 'two way' would
do this. I am not certain and most examples assume property is already
set up and not being created and updated.
}
Code Class for GridData:
class PersonName
{
public string Name { get; set; }
}
class GridData
{
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
var list = be.tePersons.Select(x => new PersonName { Name = x.FirstName });
Results = new ObservableCollection<PersonName>(list);
}
}
}
To use binding like this, you need to:
Set the DataContext correctly on the DataGrid (or on one of its parent)
Implement INotifyPropertyChanged on your model class, and raise PropertyChanged in the property setter.
1)
Set your window's DataContext to the GridData object:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
this.DataContext = gd;
}
2)
Implement INotifyPropertyChanged. This ensures that your view gets notified when the Results property gets updated:
public class GridData : INotifyPropertyChanged
{
private ObservableCollection<PersonName> _results;
public ObservableCollection<PersonName> Results
{
get { return _results; }
set
{
_results = value;
RaisePropertyChanged("GridData");
}
}
// ...
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
#endregion
}
Then you can simply bind to the path relative to the data context.
<DataGrid ItemsSource="{Binding Results}" />
Note that you don't need two-way binding in this case -- that's for propagating changes from the View back to your model (ie, most useful for when there's a UI control like a text box or checkbox).
Here is an example (I used Window, but it will work the same for UserControl)
Xaml:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding ElementName=UI, Path=GridData.Results, Mode=TwoWay}" />
</Grid>
</Window>
or id you want the whole DataContext:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" DataContext="{Binding ElementName=UI, Path=GridData}" ItemsSource="{Binding Results}" />
</Grid>
</Window>
Code:
You will have to implement INotifyPropertyChanged so the xaml knows GridData has changed
The ObservableCollection inside GridData as this function built-in so anytime you add remove items they will update the DataGrid control
public partial class MainWindow : Window , INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
GridData = new GridData { Results = new ObservableCollection<PersonName>() };
GridData.Results.Add(new PersonName { Name = "Test1" });
GridData.Results.Add(new PersonName { Name = "Test2" });
}
private GridData _gridData;
public GridData GridData
{
get { return _gridData; }
set { _gridData = value; NotifyPropertyChanged("GridData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Classes:
I made a small change to the update method, so it just clears and updates the existing ObservableCollection, otherwise you would have to Implement INotifypropertyChanged to this class if you assign a new ObservableCollection.
public class PersonName
{
public string Name { get; set; }
}
public class GridData
{
public GridData()
{
Results = new ObservableCollection<PersonName>()
}
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
// Just update existing list, instead of creating a new one.
Results.Clear();
be.tePersons.Select(x => new PersonName { Name = x.FirstName }).ToList().ForEach(item => Results.Add(item);
}
}
}

WPF C# binding code - why doesn't this simple example work?

I've attached some WPF C# binding code - why doesn't this simple example work? (just trying to understanding binding to a custom object). That is when clicking on the button to increase the counter in the model, the label isn't updated.
<Window x:Class="testapp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Height="23" HorizontalAlignment="Left" Margin="20,12,0,0"
Name="testButton" VerticalAlignment="Top" Width="126"
Click="testButton_Click" Content="Increase Counter" />
<Label Content="{Binding Path=TestCounter}" Height="37"
HorizontalAlignment="Right" Margin="0,12,122,0"
Name="testLabel2" VerticalAlignment="Top"
BorderThickness="3" MinWidth="200" />
</Grid>
</Window>
namespace testapp1
{
public partial class MainWindow : Window
{
public TestModel _model;
public MainWindow()
{
InitializeComponent();
InitializeComponent();
_model = new TestModel();
_model.TestCounter = 0;
this.DataContext = _model;
}
private void testButton_Click(object sender, RoutedEventArgs e)
{
_model.TestCounter = _model.TestCounter + 1;
Debug.WriteLine("TestCounter = " + _model.TestCounter);
}
}
public class TestModel : DependencyObject
{
public int TestCounter { get; set; }
}
}
thanks
For this simple example, consider using INotifyPropertyChanged and not DependencyProperties!
UPDATE
If you do want to use DPs, use the propdp snippet in VS2010 or Dr WPF's snippets for VS2008?
TestCounter needs to be a DepenencyProperty
public int TestCounter
{
get { return (int)GetValue(TestCounterProperty); }
set { SetValue(TestCounterProperty, value); }
}
// Using a DependencyProperty as the backing store for TestCounter.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestCounterProperty =
DependencyProperty.Register
("TestCounter",
typeof(int),
typeof(TestModel),
new UIPropertyMetadata(0));
You can implement the INotifyPropertyChanged interface in the System.ComponentModel namespace. I usually implement a Changed method that can take a number of property names and check for the event not being set. I do that because sometimes I have multiple properties that depend on one value and I can call one method from all of my property setters.
For instance if you had a Rectangle class with Width and Height properties and an Area read-only property that returns Width * Height, you could put Changed("Width", "Area"); in the property setter for Width.
public class TestModel : INotifyPropertyChanged
{
int m_TestCounter;
public int TestCounter {
get {
return m_TestCounter;
}
set {
m_TestCounter = value;
Changed("TestCounter");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
void Changed(params string[] propertyNames)
{
if (PropertyChanged != null)
{
foreach (string propertyName in propertyNames)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

Categories