I have a WPF window with some text boxes and a DataGrid. The DataGrid gets filled with Data, but I need that, when the user clicks a cell in that DataGrid, the program detects the line and re-fills the text boxes with the data in that line.
For example, there is an ID, Name and BirthDate textbox. When the user clicks any cell in a given line, the text boxes ID, Name and BirthDate's values must become the values of their respective columns (ID, Name, BirthDate) in the line selected.
I've looked around for an answer to this, but I only seem to find answers relating to WinForms, and the DataGrid in WinForms and in WPF work quite differently, code-wise.
You can simply use Binding using the ElementName property to do this for you... no code behind needed:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<DataGrid Name="DataGrid" ItemsSource="{Binding YourDataCollection}" />
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding SelectedItem.Id,
ElementName=DataGrid}" />
<TextBlock Grid.Row="1" Text="{Binding SelectedItem.Name,
ElementName=DataGrid}" />
<TextBlock Grid.Row="2" Text="{Binding SelectedItem.BirthDate,
ElementName=DataGrid}" />
</Grid>
</Grid>
Here I made a tiny illustration following your original example:
//XAML
<Grid>
<DataGrid ItemsSource="{Binding Persons}" Margin="0,0,136,0" SelectedItem="{Binding SelectedPerson}"></DataGrid>
<Label Content="{Binding SelectedPerson.Id}" HorizontalAlignment="Left" Margin="400,35,0,0" VerticalAlignment="Top" Width="90" Height="26"/>
<Label Content="{Binding SelectedPerson.Name}" HorizontalAlignment="Left" Margin="400,97,0,0" VerticalAlignment="Top" Width="90" Height="24"/>
<Label Content="{Binding SelectedPerson.BirthDate}" HorizontalAlignment="Left" Margin="400,66,0,0" VerticalAlignment="Top" Width="90" Height="26"/>
</Grid>
//.cs
public MainWindow()
{
InitializeComponent();
DataContext = new PersonViewModel();
}
//ViewModel
public class PersonViewModel : INotifyPropertyChanged
{
private Person _selectedPerson;
public List<Person> Persons { get; set; }
public Person SelectedPerson
{
get { return _selectedPerson; }
set { _selectedPerson = value; OnPropertyChanged("SelectedPerson"); }
}
public PersonViewModel()
{
Persons = new List<Person>
{
new Person(){Id = 1,BirthDate = DateTime.Now.AddYears(-30),Name = "Mark"},
new Person(){Id = 2,BirthDate = DateTime.Now.AddYears(-40), Name = "Sophy"},
new Person(){Id = 3,BirthDate = DateTime.Now.AddYears(-50), Name = "Bryan"},
};
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
// Model
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
}
Related
I have a user list in MainWindow. After pressing the preview button, a non-modal window for data editing opens. Updated data are changed in real time in the main window. The question is how to bind the windows so that because the user changes from the list in the main window, he changes in real time in an already open non-modal window.
WPF does not recommend coding business logic directly in xaml.cs
files.
It is recommended that you write code using the MVVM pattern
ViewModel
public class podgladUzytkownika : INotifyPropertyChanged
{
private string imie;
private string nazwisko;
private string mail;
public string Mail
{
get => mail;
set => this.SetValue(ref mail, value);
}
public string Nazwisko
{
get => nazwisko;
set => this.SetValue(ref nazwisko, value);
}
public string Imie
{
get => imie;
set => this.SetValue(ref imie, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void SetValue<T>(ref T oldValue, T newValue, [CallerMemberName] string propertyName = null)
{
oldValue = newValue;
OnPropertyChanged(propertyName);
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainWindowViewModel
{
private int refreshCount;
public ICommand RefreshCommand { get; }
public ICommand PodgladUzytkownikaShow { get; }
public podgladUzytkownika data { get; }
public MainWindowViewModel()
{
data = new podgladUzytkownika();
PodgladUzytkownikaShow = new Command(PodgladUzytkownikaShowExecute);
RefreshCommand = new Command(RefreshCommandExecute);
}
private void PodgladUzytkownikaShowExecute(object obj)
{
var window = new Window();
window.DataContext = data;
window.Show();
}
private void RefreshCommandExecute(object obj)
{
// Data updates are passed to the view
refreshCount++;
data.Imie = nameof(data.Imie) + refreshCount;
data.Nazwisko = nameof(data.Nazwisko) + refreshCount;
data.Mail = nameof(data.Mail) + refreshCount;
}
}
View
// MainWindow.xaml
<StackPanel x:Name="StackPanel1">
<Button Content="PodgladUzytkownika" Command="{Binding Path=PodgladUzytkownikaShow}"/>
<Button Content="Refresh" Command="{Binding Path=RefreshCommand}"/>
</StackPanel>
// window.xaml
<StackPanel >
<TextBlock Text="{Binding Path=Imie }"/>
<TextBlock Text="{Binding Path=Nazwisko }"/>
<TextBlock Text="{Binding Path=Mail }"/>
</StackPanel>
Demo
this.MainWindow.DataContext =new MainWindowViewModel();
After chasing one problem after another with your comments, the problem is your code is not well-designed. Using data bindings (one of the prime benefits of WPF), you can stop chasing your tail with trying to figure out how to update one UI when data changes. Here is a simplified version of your code that will always ensure the UI matches the data you wish to manipulate.
MainWindow.xaml
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Label Margin="3" Grid.ColumnSpan="3">Lista użytkowników</Label>
<Button Margin="3" Padding="3" Grid.Row="2" Grid.ColumnSpan="3" Click="Zamknij_Click">Zamknij</Button>
<StackPanel Margin="3" Grid.Column="2" Grid.Row="1">
<!--<Button Name="Dodaj" Click="Dodaj_Click" Margin="3" Padding="10,3" >Dodaj...</Button>-->
<!--<Button Name="Usun" IsEnabled="False" Click="Usun_Click" Margin="3" Padding="10,3" >Usuń</Button>-->
<!--<Button Name="Edytuj" IsEnabled="False" Click="Edytuj_Click" Margin="3" Padding="10,3" >Edytuj...</Button>-->
<Button Name="Podglad" IsEnabled="False" Click="Podglad_Click" Margin="3" Padding="10,3" >Podgląd...</Button>
</StackPanel>
<ListView SelectionMode="Single" SelectionChanged="Selection_Changed" Name="lv_uzytkownicy" Margin="3" Grid.Row="1">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Imię"
DisplayMemberBinding="{Binding Imie}"/>
<GridViewColumn Header="Nazwisko"
DisplayMemberBinding="{Binding Nazwisko}" />
<GridViewColumn Header="Mail"
DisplayMemberBinding="{Binding Mail}"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<GridSplitter Grid.Column="1" Grid.Row="1" Width="5" ResizeDirection="Columns" HorizontalAlignment="Center"/>
</Grid>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
public partial class MainWindow : Window
{
public ObservableCollection<Uzytkownik> listaUzytkownikow = new ObservableCollection<Uzytkownik>();
Podglad_Uzytkownika podgladUzytkownika;
public MainWindow()
{
InitializeComponent();
lv_uzytkownicy.ItemsSource = listaUzytkownikow;
listaUzytkownikow.Add(new Uzytkownik("Mietek", "Żul", "sikalafa#wp.pl"));
listaUzytkownikow.Add(new Uzytkownik("Franek", "Alpinista", "halo#gmail.pl"));
listaUzytkownikow.Add(new Uzytkownik("Stefan", "Ulążka", "mam.to#o2.pl"));
this.DataContext = this;
}
private void Podglad_Click(object sender, RoutedEventArgs e)
{
podgladUzytkownika = new Podglad_Uzytkownika();
podgladUzytkownika.DataContext = lv_uzytkownicy.SelectedItem;
podgladUzytkownika.Show();
}
private void Zamknij_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void Selection_Changed(object sender, SelectionChangedEventArgs e)
{
if (lv_uzytkownicy.SelectedItems.Count > 0) Podglad.IsEnabled = true;
else Podglad.IsEnabled = false;
if (podgladUzytkownika != null && podgladUzytkownika.IsVisible)
{
podgladUzytkownika.DataContext = lv_uzytkownicy.SelectedItem;
}
}
}
Podglad_Uzytkownika.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Margin="3">Imię</Label>
<Label Margin="3" Grid.Row="1">Nazwisko</Label>
<Label Margin="3" Grid.Row="2">Email</Label>
<TextBox Name="imieTextBox" Text="{Binding Imie, UpdateSourceTrigger=PropertyChanged}" Margin="3" Grid.Column="1"/>
<TextBox Name="nazwiskoTextBox" Text="{Binding Nazwisko, UpdateSourceTrigger=PropertyChanged}" Margin="3" Grid.Column="1" Grid.Row="1"/>
<TextBox Name="mailTextBox" Text="{Binding Mail, UpdateSourceTrigger=PropertyChanged}" Margin="3" Grid.Column="1" Grid.Row="2"/>
<Grid HorizontalAlignment="Center" Grid.ColumnSpan="2" Grid.Row="3" Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="pierwsza" />
</Grid.ColumnDefinitions>
<Button Margin="3" Padding="20, 5" Name="Podglad" Click="Podglad_Click" IsDefault="True">Zamknij</Button>
</Grid>
</Grid>
Podglad_Uzytkownika.xaml.cs
public partial class Podglad_Uzytkownika : Window
{
public Podglad_Uzytkownika()
{
InitializeComponent();
}
private void Podglad_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
Uzytkownik.cs
public class Uzytkownik : INotifyPropertyChanged
{
private string imie;
private string nazwisko;
private string mail;
public Uzytkownik(string imie, string nazwisko, string mail)
{
this.Imie = imie;
this.Nazwisko = nazwisko;
this.Mail = mail;
}
public string Imie { get => this.imie; set { this.imie = value; OnPropertyChanged(); } }
public string Nazwisko { get => this.nazwisko; set { this.nazwisko = value; OnPropertyChanged(); } }
public string Mail { get => this.mail; set { this.mail = value; OnPropertyChanged(); } }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I am having a very difficult time trying to get this to two-way bind to the item in the collection. The strange thing here is that the control shows the label but when I type anything in the text box it doesn't set the underlining value. Can someone please tell me what I'm doing wrong here.
<ItemsControl Grid.Row="0" Grid.RowSpan="2" ItemsSource="{Binding QueryObject.RequiredParameters}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type queryModels:QueryObjectParameter}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding Label}"></Label>
<TextBox Grid.Row="1" Text="{Binding Value, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've tried these different types.
{Binding Path=Value, RelativeSource={RelativeSource Self} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding XPath=DataContext.Value, RelativeSource={RelativeSource Self} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding XPath=Value, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding Path=Value, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
Thank you for your help!
Edit:
I have been asked to add a better example to this so I created a very easy example. Note: the underlining model is called, but it doesn't set the model in the ViewModel.
public class MainWindowViewModel:INotifyPropertyChanged
{
public MainWindowViewModel()
{
PersonQuery = new PersonQuery();
Command = new DelegateCommand(CommandAction);
}
private void CommandAction()
{
MessageBox.Show(PersonQuery.Parameters.First().ToString());
}
public DelegateCommand Command { get; set; }
public PersonQuery PersonQuery { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Parameter
{
public Parameter(string label)
{
Label = label;
}
public string Label { get; set; }
public object Value { get; set; }
public override string ToString()
{
return $"{Label}:{Value}";
}
}
public class PersonQuery
{
public Parameter[] Parameters => new[] {new Parameter("Test"),};
}
XAML:
<Button Content="Run" Command="{Binding Command}"></Button>
<ItemsControl Grid.Row="1" ItemsSource="{Binding PersonQuery.Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Parameter}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Content="{Binding Label}"></Label>
<TextBox Grid.Row="1" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here's a simplified example where an ItemsControl is used and property update works both ways.
Simple Data class:
public class Data
{
public int Id { get; set; }
public string Name { get; set; }
}
Here I implemented INotifyPropertyChanged on my MainWindow for convenience, but you should really use a ViewModel and do this there.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Data> _dataList = null;
public ObservableCollection<Data> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
public MainWindow()
{
InitializeComponent();
DataList = new ObservableCollection<Data>
{
new Data() { Id = 1, Name = "Dan" },
new Data() { Id = 2, Name = "Julie" }
};
DataContext = this;
}
}
The XAML is super simple:
<ItemsControl ItemsSource="{Binding DataList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Id}"/>
<TextBox Grid.Column="1" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
NOTE:
Check in your code if you have:
Implemented INotifyPropertyChanged
Use an ObservableCollection for your list.
I have a ListView with textBox in one of the columns. User input on the textbox supposed to update the underlying data, that's what my requirement is, but it's not happening. I tried a work around as shown all the way below, it seems to work but I am guessing that the binding itself should take care of it without the work around.
XAML:
<ListView x:Name="listView" Grid.Row="1" ItemsSource="{Binding}" SelectionMode="Multiple">
<ListView.ItemTemplate>
<DataTemplate>
<Border Background="{Binding BackGround}">
<Grid x:Name="row">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="tblDescription"
Grid.Row="0"
Grid.Column="0"
Margin="1"
TextWrapping="Wrap"
Text="{Binding Description}"
FontSize="{StaticResource TextStyleMediumFontSize}"
Foreground="Black"/>
<TextBlock x:Name="tblItemNumber"
Grid.Row="0"
Grid.Column="1"
Margin="1"
Text="{Binding ItemNumber}"
FontSize="{StaticResource TextStyleMediumFontSize}"
FontStyle="Italic"
Foreground="Gray"/>
<TextBox x:Name="tbQuantity"
Grid.Row="1"
Grid.Column="1"
Margin="1"
Text="{Binding Quantity}"
IsEnabled="{Binding IsEnabled}"
FontSize="{StaticResource TextStyleLargeFontSize}"
BorderBrush="DarkGray"
Foreground="Black"
InputScope="Number"
Tag="{Binding RowNumber}"
TextChanged="tbQuantity_TextChanged"
>
<TextBox.Header>Quantity</TextBox.Header>
</TextBox>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
DataRow class:
public class DataRowBase : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Properties
public int RowNumber { get; set; }
public string ItemNumber { get; set; }
public string Description { get; set; }
private string _Quantity;
public string Quantity
{
get { return _Quantity; }
set {
_Quantity = value;
RaisePropertyChanged("Quantity");
}
}
#endregion
}
Work Around:
private void tbQuantity_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox tb = (TextBox)sender;
int rownumber = tb.Tag.ToString().ToInt();
UpdateQuantity(rownumber, value: tb.Text);
}
private void UpdateQuantity(int rownumber, string value)
{
try
{
DataRow datarow = OriginalSource.Where(o => o.RowNumber == rownumber).FirstOrDefault();
if (datarow != null)
{
datarow.Quantity = value;
}
}
catch (Exception ex)
{
}
}
The default Binding is OneWay, for a TwoWay you will have to declare it, for example:
Text="{Binding ItemNumber, Mode=TwoWay}"
I'm playing around with WPF and LINQ, and stuck at the following point with no clue on how to show a single record, all of the examples I found show how to present a list, but not a single record.
I have prepared a user control that should show one record at a time, in my example a Customer, showing its' Id, Given Name and Surname, here it is:
<UserControl x:Class="SportsClubsManagement.CustomersManagement.CustomerData"
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="300" d:DesignWidth="500">
<Grid DataContext="Binding Customer">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Id" Height="30" Width="70" />
<TextBox Grid.Row="0" Grid.Column="1" Height="20" Width="70" Name="Id" Text="{Binding Path=Id}" />
<Label Grid.Row="1" Grid.Column="0" Content="Given Name" Height="30" Width="70" />
<TextBox Grid.Row="1" Grid.Column="1" Height="20" Width="70" Name="GivenName" Text="{Binding Path=GivenName}" />
<Label Grid.Row="1" Grid.Column="2" Content="Surname" Height="30" Width="70" />
<TextBox Grid.Row="1" Grid.Column="3" Height="20" Width="70" Name="Surname" Text="{Binding Path=Surname}" />
<Button Grid.Row="2" Grid.Column="0" Content="New record" />
<Button Grid.Row="2" Grid.Column="3" Content="Next record" />
<Button Grid.Row="2" Grid.Column="2" Content="Prev record" />
</Grid>
</UserControl>
I also have created a table with several records and a class to represent a customer which also implements INotifyPropertyChanged.
[Table(Name = "Customers")]
public class Customer : INotifyPropertyChanged
{
#region Fields
private String id;
private String givenName;
private String surname;
#endregion
#region Properties
[Column(IsPrimaryKey=true, Storage="id", Name="Id")]
public String Id
{
get { return this.id; }
}
[Column(Storage="givenName", Name="GivenName")]
public String GivenName
{
get { return this.givenName; }
set {
if (this.givenName != value)
{
this.givenName = value;
this.OnPropertyChanged("GivenName");
}
}
}
[Column(Storage="surname", Name="Surname")]
public String Surname
{
get { return this.surname; }
set {
if (this.surname != value)
{
this.surname = value;
this.OnPropertyChanged("Surname");
}
}
}
#endregion
#region INotifyPropertyChanged Members"
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
Now in the code behind the user control I have a realy simple code that connect to the db and gets a record:
public partial class CustomerData : UserControl
{
private int curCustSeqNum;
private Data.Customer customer;
public Data.Customer Customer
{
get
{
return customer;
}
set
{
this.customer = value;
}
}
private IQueryable<Data.Customer> custQuery;
public CustomerData()
{
InitializeComponent();
DataContext db = new DataContext(#"server=WIN-EL78137MUMS\SQLEXPRESS;database=PlayGround;integrated security=SSPI");
// Get a typed table to run queries.
Table<Data.Customer> Customers = db.GetTable<Data.Customer>();
//db.Log = Console.Out;
custQuery =
from cust in Customers
orderby cust.Id
select cust;
curCustSeqNum = 0;
Customer = custQuery.Skip(curCustSeqNum).Take(1).First();
}
}
But how can I bind the textboxes to the current selected Customer? You can see my last guess, after defining resources etc. But every time I miss something. I would like to know how can I achieve that in XAML, and in code behind. Basically after that I would be able to use the curCustSeqNum to skip to the following record.
The CustomerData does not implement INotifyPropertyChanged so when you set Customer in the constructor the xaml has no Idea it has changed.
Example:
public partial class CustomerData : UserControl, INotifyPropertyChanged
{
private int curCustSeqNum;
private Data.Customer customer;
private IQueryable<Data.Customer> custQuery;
public Data.Customer Customer
{
get { return customer; }
set { this.customer = value; OnPropertyChanged("Customer"); }
}
public CustomerData()
{
InitializeComponent();
// set the datacontext
DataContext = this;
................
Customer = custQuery.Skip(curCustSeqNum).Take(1).First();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
also mentioned by someone else, you DataContext binding on the Grid is invalid
<Grid DataContext="Binding Customer">
should be
<Grid DataContext="{Binding Customer}">
Typically you'd make a "CurrentItem" object inside of your ViewModel in which you'd initialize on a SelectionCommand. In your case, you'd bind your "Customer" on your SelectionCommand in which you'd run a Customer.Get(id) command to obtain the current customer. You can then bind your controls to the "CurrentCustomer" object of the fields for each control.
Try set your Datacontext to
DataContext="{Binding Path=Customer}"
I realise that path is implied, however i have run into this problem myself and this solved it for me.
As I'm learning ... I have created a simple data binding project which works fine with on piece of data e.g. firstName. However, when I'm trying to use lastName compilers throws a runtime error as
** Cannot evaluate expression because the current thread is in a stack overflow state.**
this is the code. As you see 2nd field (last name) is commented out since it is causing stack overflow. any comment is appreciated.
public partial class MainWindow : Window
{
Person p;
public MainWindow()
{
InitializeComponent();
p = new Person();
p.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(p_PropertyChanged);
this.DataContext = p;
p.FirstName = p.OriginalFirstName;
p.LastName = p.OriginalLastName;
}
void p_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
stat1.Text = (p.OriginalFirstName == p.FirstName) ? "Original" : "Modified";
//stat2.Text = (p.OriginalLastName == p.LastName) ? "Original" : "Modifined";
}
}
EDIT:
class Person : INotifyPropertyChanged
{
public string OriginalFirstName = "Jim";
public string OriginalLastName = "Smith";
private string _firstName;
#region FirstName
public string FirstName
{
get { return _firstName; }
set
{
if (value != null)
{
_firstName = value;
NotifyTheOtherGuy(FirstName);
}
}
}
#endregion FirstName
private string _lastName;
#region LastName
public string LastName
{
get { return _lastName; }
set
{
if (value != null)
{
_lastName = value;
NotifyTheOtherGuy(LastName);
}
}
}
#endregion LastName
public Person()
{
}
public event PropertyChangedEventHandler PropertyChanged;
void NotifyTheOtherGuy(string msg)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(msg));
}
}
}
XAML:
<Window x:Class="FullNameDataBinding.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>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="First Name:"/>
<Label Grid.Row="1" Content="Last Name:"/>
<TextBox Grid.Column="1" Grid.Row="0" Background="Yellow" Margin="5" FontWeight="Bold" Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock x:Name="stat1" Grid.Column="2" />
<TextBox x:Name="stat2" Grid.Column="1" Grid.Row="1" Background="Yellow" Margin="5" FontWeight="Bold" Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Column="2" Grid.Row="1" />
</Grid>
</Window>
I think the error in this chunk of your XAML:
<TextBox Grid.Column="1" Grid.Row="0" Background="Yellow" Margin="5" FontWeight="Bold" Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock x:Name="stat1" Grid.Column="2" />
<TextBox x:Name="stat2" Grid.Column="1" Grid.Row="1" Background="Yellow" Margin="5" FontWeight="Bold" Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Column="2" Grid.Row="1" />
I think you want the last TextBlock to have x:Name="stat2", not the TextBox before it.
When you change the LastName, your PropertyChanged event handler is called, which changes the text value of stat2. Because stat2 is the TextBox whose value is bound to LastName using a TwoWay binding, this causes the binding mechanism to send the value you set back to the view-model. This causes another PropertyChanged event to fire, which changes the value of stat2, which causes another PropertyChanged event to fire.... This endless cycle doesn't stop, which is why you get the stack-overflow error.
You don't get any such stack overflow with FirstName because stat1 is a TextBlock with no binding on its Text property.
Every time a property is changed, you change a property (the text value) which fires off another property changed event, which changes the text property, which fires off the ....
Do you see where this is going?
You either need to disable the firing of the event when you change the text property, or not change it in the context of the property changed event handler.
Since we don't have the details of your Person class we don't know if it already support some mechanism for disabling event firing, or for changing a value without firing off events. If one doesn't exist, you may need to create your own. How you do that will depend on the implementation of that class.
I suppose p_PropertyChanged method should be static and the following changes to your method could work without problems.
static void p_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
MainWindow w = (MainWindow) sender;
w.stat1.Text = (w.p.OriginalFirstName == w.p.FirstName) ? "Original" : "Modified";
//stat2.Text = (p.OriginalLastName == p.LastName) ? "Original" : "Modifined";
}
but it would be better if you post part of the XAML code because you probably could get the same result in a cleaner way.
When working with WPF you should nearly forget some winform programming code practices. I suppose you should write your code using mainly Bindings and DependenciesProperty.
EDIT
As someone else said you probably assigned the stat1 name to the wrong object and this started the infinite recursion that thrown the StackOverflowException.
In the Person class, PropertyChanged must be called with the name of the property not with its value.
Here follows a working sample that doesn't stop with that problem, I also wanted to show you how the WPF platform allows you to pull all the main elaboration tasks out of the View class and allowing you to move that phase in the Model class following MVVM paradigm
In the person class there's the addition of the Check properties: it evaluates the condition of your original propertychanged method that was firing the exception. Every time in the class there is a change on FirstName or LastName property if fires changed event for the change of CheckFirstName or CheckLastName. In this way you don't need to handle change events in the View class for this purpose because the evaluation of the condition is done already by this model class and the result is available and ready for a bound object.
public class Person : INotifyPropertyChanged
{
public string OriginalFirstName = "Jim";
public string OriginalLastName = "Smith";
private string _firstName;
#region FirstName
public string FirstName
{
get { return _firstName; }
set
{
if (value != null)
{
_firstName = value;
NotifyTheOtherGuy("CheckFirstName");
}
}
}
#endregion FirstName
private string _lastName;
#region LastName
public string LastName
{
get { return _lastName; }
set
{
if (value != null)
{
_lastName = value;
NotifyTheOtherGuy("CheckLastName");
}
}
}
#endregion LastName
public string CheckFirstName
{
get
{
return (FirstName==OriginalFirstName) ? "Original": "Modified";
}
}
public string CheckLastName
{
get
{
return (LastName==OriginalLastName) ? "Original": "Modified";
}
}
public Person()
{
}
public event PropertyChangedEventHandler PropertyChanged;
void NotifyTheOtherGuy(string msg)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(msg));
}
}
}
The MainWindow class: all the elaboration tasks are removed from this class and there's only the definition of a DependecyProperty for the Person object.
public partial class MainWindow : Window
{
public static readonly DependencyProperty MyPersonProperty;
static MainWindow()
{
MyPersonProperty = DependencyProperty.Register("MyPerson", typeof(Person), typeof(MainWindow));
}
Person MyPerson
{
set
{
SetValue(MyPersonProperty,value);
}
get
{
return GetValue(MyPersonProperty) as Person;
}
}
public MainWindow()
{
MyPerson = new Person();
InitializeComponent();
}
}
The MainWindow XAML: each component is bound the the Person DependencyProperty in the right way. The TextBoxes are bound to update the Person properties values and the TextBlocks are bound to fetch the result of the Check properties that (as said before) notify its changes after the other properties of the Person class are changed.
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="TryPrj.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prj="clr-namespace:TryPrj"
Title="TryPrj"
Height="300"
Width="300"
x:Name="myWindow">
<Grid>
<Grid.RowDefinitions>
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="100" />
<ColumnDefinition
Width="100" />
<ColumnDefinition
Width="100" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Grid.Row="0"
Content="First Name:" />
<Label
Grid.Row="1"
Content="Last Name:" />
<TextBox
Grid.Column="1"
Grid.Row="0"
Background="Yellow"
Margin="5"
FontWeight="Bold"
Text="{Binding Path=MyPerson.FirstName, Mode=OneWayToSource, ElementName=myWindow, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock
Grid.Column="2"
Text="{Binding Path=MyPerson.CheckFirstName, Mode=OneWay, ElementName=myWindow}"
/>
<TextBox
Grid.Column="1"
Grid.Row="1"
Background="Yellow"
Margin="5"
FontWeight="Bold"
Text="{Binding Path=MyPerson.LastName, Mode=OneWayToSource, ElementName=myWindow, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock
Grid.Column="2"
Grid.Row="1"
Text="{Binding Path=MyPerson.CheckLastName, Mode=OneWay, ElementName=myWindow}" />
</Grid>
</Window>