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.
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 teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.
In the first Class:
I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works.
I have a Command bound to a Button to move items from one Collection to the other, which works as expected.
In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).
On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.
I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?
I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???
<Window x:Class="MultipleClassDragAndDrop.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:MultipleClassDragAndDrop"
xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="716" Width="500">
<Window.Resources>
<ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Button Content="Test Command" Command="{Binding Test_Command}"/>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightPink" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Grid Grid.Row="0" Grid.Column="2" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}"
MouseMove="ListView1_MouseMove"
AllowDrop="True" Drop="ListView2_Drop" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightBlue" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
BackCode
using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
int lb_itemIndex = ListView1.SelectedIndex;
// Package the data.
DataObject data = new DataObject();
data.SetData("Int", lb_itemIndex);
data.SetData("Object", this);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
private void ListView2_Drop(object sender, DragEventArgs e)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");
base.OnDrop(e);
int index = (int)e.Data.GetData("Int");
// Call A Method In A Different Class
objMultiColumnViewModel.AddAndRemove(index);
e.Handled = true;
}
}
}
My ViewModel Class
using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;
namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
public string _JobID;
public string JobID
{
get { return _JobID; }
set
{ _JobID = value; NotifyPropertyChanged("JobID"); }
}
public string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set
{ _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
}
}
public partial class MultiColumnViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
//Test Command
private ICommand _Test_Command;
public ICommand Test_Command
{
get
{
if (_Test_Command == null)
{
_Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
}
return _Test_Command;
}
}
public bool CanExecuteTest_Command(object parameter)
{
return true;
}
public void ExecuteTest_Command(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
AddAndRemove(0);
Mouse.OverrideCursor = Cursors.Arrow;
}
public void AddAndRemove(int selectedIndex)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");
ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);
ActiveJobListView1.RemoveAt(selectedIndex);
foreach (var item in ActiveJobListView1)
{
System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
}
System.Console.WriteLine($" ");
foreach (var item in ActiveJobListView2)
{
System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
}
}
public MultiColumnViewModel()
{
ActiveJobListView1 = new ObservableCollection<ActiveJob>();
ActiveJobListView2 = new ObservableCollection<ActiveJob>();
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
}
#region Properties
private ObservableCollection<ActiveJob> _ActiveJobListView1;
public ObservableCollection<ActiveJob> ActiveJobListView1
{
get { return _ActiveJobListView1; }
set
{
_ActiveJobListView1 = value;
NotifyPropertyChanged("ActiveJobListView1");
}
}
private ObservableCollection<ActiveJob> _ActiveJobListView2;
public ObservableCollection<ActiveJob> ActiveJobListView2
{
get { return _ActiveJobListView2; }
set
{
_ActiveJobListView2 = value;
NotifyPropertyChanged("ActiveJobListView2");
}
}
#endregion
}
}
When binding to a Collection, there are 3 kinds of ChangeNotification you need:
The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.
Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf
You have issues with different instances of the same class.
Change:
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
To:
var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
and it should work
EDIT:
What you are doing is strongly against MVVM principals.
EDIT-2
I had to do some modification to you code to make it work:
In your XAML:
<Window.DataContext>
<local:MultiColumnViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
In your MainWindow.cs:
public MainWindow()
{
InitializeComponent();
objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
}
private MultiColumnViewModel objMultiColumnViewModel;
I'm dynamically create two textboxes and a textblock. The user first clicks a button which adds a row of controls and then inputs numbers in each textbox. The sum of the two boxes for a given row will be displayed in a text block.
Here is the XAML.
<Window x:Class="ModelBuilder_080614.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ModelBuilder_080614"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<!-- this is a comment -->
<local:MainWindowViewModel />
</Window.DataContext>
<Canvas>
<Button Canvas.Top="21" Canvas.Left="20" Content="Add TextBox" Command="{Binding TestCommand}"/>
<ItemsControl Canvas.Top="50" Canvas.Left="50" ItemsSource="{Binding SomeCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Grid.Row="0" Text="{Binding Path=.}"/>
<TextBox Grid.Column="1" Grid.Row="0" Name="Bench" Text="{Binding Path=.}"/>
<TextBlock Grid.Column="2" Grid.Row="0" Text="{Binding <!-- I'm LOST -->}"/>
<!-- I want this TextBlock to sum the Two TextBlocks -->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Window>
And here is my Model and ViewModel in C#.
using System;
using System.ComponentModel;
using System.Windows.Input;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows.Data;
using MicroMvvm;
namespace ModelBuilder_080614
{
public class MainWindowViewModel
{
public ObservableCollection<Model> SomeCollection { get; set; }
public ICommand TestCommand { get; private set; }
public MainWindowViewModel()
{
SomeCollection = new ObservableCollection<Model>();
TestCommand = new RelayCommand<object>(CommandMethod);
}
private void CommandMethod(object parameter)
{
SomeCollection.Add(new Model());
}
}
public class Model : INotifyPropertyChanged
{
double _actual;
double _bench;
double _active;
public double Actual
{
get { return _actual; }
set { _actual = value; }
}
public double Bench
{
get { return _bench; }
set { _bench = value; }
}
public double Active
{
get { return _active; }
set { _active = Actual - Bench; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
How do you bind the contents of the textboxes to display the sum of them in the TextBlock?
Bind to the properties in your ViewModel: (I removed the grid settings to make the example clearer)
<TextBox Text="{Binding Path=Actual, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Path=Bench, UpdateSourceTrigger=PropertyChanged" />
<TextBlock Text="{Binding Path=Active" />
Then when your values change, raise the notification on the "Active" property so it updates. You only need a "getter" on the "Active" property... when you call OnPropertyChanged(), it will perform the calculation and update the field for you.
double _actual;
double _bench;
public double Actual
{
get { return _actual; }
set
{
_actual = value;
OnPropertyChanged("Active");
}
}
public double Bench
{
get { return _bench; }
set
{
_bench = value;
OnPropertyChanged("Active");
}
}
public double Active
{
get { return Actual - Bench; }
}
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; }
}
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>