Reputation: 2537
Please allow me to present a simplified version of my problem:
Lets say I have a main window called MainWindow
in which I would like to display some Person
objects in MainWindow
. Now, in order to instantiate these Person
objects I need a bunch of different fields such as name, age, profession, favourite food, etc...
Here is my solution:
I try to get all input fields and instantiate a Person
in a secondary window and then send back the result to the main form.
MainWindow
has a public method as follows:
public void (Person input)
{
// use the fields in input to add details to window
}
I have another window in the project called PersonInput
that takes in its constructor a reference to a MainWindow
and saves it in a field.
private MainWindow owner;
public PersonInput(MainWindow parent)
{
InitializeComponent();
owner = parent;
}
PersonInput
has a number of input fields corresponding to the required fields of a Person
object.
in addition it has a button called "AddPerson" with an associated onClick event handler as follows: (pseudoCode)
private void button_Click(object sender, RoutedEventArgs e)
{
//get all fields from this form..
String enteredName = this.nameText.Text;
//get more fields....
Person p = new Person(...);
//owner is MainWindow, send Back the Person so details can be displayed
owner.addPerson(p);
this.Close();
}
as you would expect, MainWindow
has a Button named "AddPersonButton" which has an on click event handler like this:
private void button_Click(object sender, RoutedEventArgs e)
{
PersonInput x = new PersonInput(this); //pass this as a reference
//so this window can send us back the result when they have it
x.Show(); //open the child window so user can enter information
}
While this method works, I am quite convinced it is not the best practice way to do it. I would like to learn the idiomatic .net WPF way of doing this. Please enlighten me
Upvotes: 0
Views: 2087
Reputation: 37059
PersonInput.xaml.cs
public class PersonInput : Window
{
public void PersonInput()
{
InitializeComponent();
Owner = Application.Current.MainWindow;
}
public static Person ShowDialog(Person initializer)
{
var vm = new PersonViewModel(initializer);
var dlg = new PersonInput() { DataContext = vm };
if (dlg.ShowDialog().GetValueOrDefault(false))
{
return vm.ToPerson();
}
return null;
}
private void OK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
PersonInputViewModel.cs
public class PersonViewModel : ViewModelBase
{
public PersonViewModel(Person person = null)
{
if (person != null)
{
// Assuming Person has FirstName and LastName properties
FirstName = person.FirstName;
LastName = person.LastName;
// etc. etc. for all the rest
}
}
public Person ToPerson()
{
return new Person()
{
FirstName = this.FirstName,
LastName = this.LastName,
// etc. etc. for all other properties
};
}
private string _firstName = null;
public string FirstName {
get { return _firstName; }
set {
if (value != _firstName) {
_firstName = value;
OnPropertyChanged(nameof(FirstName));
}
}
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
PersonInput.xaml
<Window xmlns:blahblahblah="Blah blah blah" etc etc
Title="Person"
Height="640"
Width="480"
ShowInTaskbar="False"
ResizeMode="CanResizeWithGrip"
WindowStartupLocation="CenterOwner"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="180" Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0">First Name</Label>
<TextBox
Grid.Row="0"
Grid.Column="1"
Text="{Binding FirstName}"
/>
<Label Grid.Row="1" Grid.Column="0">Last Name</Label>
<TextBox
Grid.Row="1"
Grid.Column="1"
Text="{Binding LastName}"
/>
<StackPanel
Orientation="Horizontal"
Grid.Column="1"
Grid.Row="10"
HorizontalAlignment="Right"
>
<Button Content="_OK" Click="OK_Click" />
<Button Content="_Cancel" Click="Cancel_Click" />
</StackPanel>
</Grid>
</Window>
Upvotes: 3
Reputation: 31
Another use MVVM practice:
public class Person {
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public class PersonListViewModel : DependencyObject {
public ObservableCollection<Person> Items { get; set; }
public Person CurrentPerson
{
get { return (Person)GetValue(CurrentPersonProperty); }
set { SetValue(CurrentPersonProperty, value); }
}
public static readonly DependencyProperty CurrentPersonProperty = DependencyProperty.Register("CurrentPerson", typeof(Person), typeof(PersonListViewModel));
public ICommand AddCommand { get; set; }
public ICommand EditCommand { get; set; }
public PersonListViewModel() {
Items = new ObservableCollection<Person>();
AddCommand = new RelayCommand(p=> add() );
EditCommand = new RelayCommand(p=> { return CurrentPerson != null; }, p => edit());
}
private void add() {
Person p= new Person();
p.Id = Items.Count();
p.Name = "New Name";
p.Birthday = DateTime.Now;
Items.Add(p);
}
private void edit() {
var viewModel = new PersonItemViewModel(CurrentPerson);
var view = new View.PersonEditWindow();
view.DataContext = viewModel;
view.Show();
}
}
public class PersonItemViewModel : DependencyObject {
Person person;
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(PersonItemViewModel) );
public DateTime Birthday
{
get { return (DateTime)GetValue(BirthdayProperty); }
set { SetValue(BirthdayProperty, value); }
}
public static readonly DependencyProperty BirthdayProperty = DependencyProperty.Register("Birthday", typeof(DateTime), typeof(PersonItemViewModel));
public PersonItemViewModel() {
}
public PersonItemViewModel(Person source) {
this.person = source;
Name = person.Name;
Birthday = person.Birthday;
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) {
base.OnPropertyChanged(e);
if (e.Property == NameProperty) {
person.Name = (string) e.NewValue;
}
if (e.Property == BirthdayProperty) {
person.Birthday = (DateTime)e.NewValue;
}
}
}
List Form:
<Window x:Class="WpfApplication1.View.PersonListWindow"
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:local="clr-namespace:WpfApplication1.View"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Title="PersonListWindow" Height="300" Width="300"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
d:DataContext="{d:DesignInstance Type=viewModel:PersonListViewModel, IsDesignTimeCreatable=True}"
>
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding Items}" SelectedItem="{Binding CurrentPerson, Mode=TwoWay}">
</DataGrid>
<StackPanel Grid.Row="1" Height="32" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Margin="4" Command="{Binding AddCommand}">Add</Button>
<Button Margin="4" Command="{Binding EditCommand}">Edit</Button>
</StackPanel>
</Grid>
Edit Form:
<Window x:Class="WpfApplication1.View.PersonEditWindow"
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:WpfApplication1.View"
mc:Ignorable="d"
Title="PersonEditWindow" Height="300" Width="300"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
d:DataContext="{d:DesignInstance Type=viewModel:PersonItemViewModel, IsDesignTimeCreatable=True}"
>
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0">Name</TextBlock>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Name}"></TextBox>
<TextBlock Grid.Row="1">Birthday</TextBlock>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Birthday}"></TextBox>
</Grid>
Upvotes: 0
Reputation: 4203
The biggest weakness of this approach is the strong coupling between MainWindow and PersonInput.
A slightly better approach would be to use Observer Pattern and have the Main Window anonymously subscribe.
Simple solution example code:
public interface IAddPersonObserver
{
void OnPersonAdded(Person person);
}
public interface IAddPersonObservable
{
void Subscribe(IAddPersonObserver observer);
void Unsubscribe(IAddPersonObserver observer);
}
public class MainWindow : IAddPersonObserver
{
...
private void button_Click(object sender, RoutedEventArgs e)
{
PersonInput x = new PersonInput();
x.Subscribe(this);
x.Show();
}
public void OnPersonAdded(Person addedPerson)
{
addPerson(addedPerson); // or whatever view update code you want
}
}
Further improvements would revolve around separating the MainWindow from knowing what view gets created or shown, and having an intermediary object (such as a PersonRepository) store/hold/provide the important business data. This is much better than having the application data actually live inside the Views and Application Windows.
Upvotes: 0