WPF Binding ListBox Master/Detail - c#

I can get this working with an XmlDataSource but not with my own classes. All I want to do is bind the listbox to my collection instance and then link the textbox to the listbox so I can edit the person's name (two-way). I've deliberately kept this as simple as possible in the hope that somebody can fill in the blanks.
XAML:
<Window x:Class="WpfListTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfListTest"
Title="Window1" Height="300" Width="600">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<ListBox />
</DockPanel>
<DockPanel Grid.Column="2">
<StackPanel>
<Label>Name</Label>
<TextBox />
</StackPanel>
</DockPanel>
</Grid>
</Window>
C# code behind:
namespace WpfListTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public People MyPeeps = new People();
public Window1()
{
InitializeComponent();
MyPeeps.Add(new Person("Fred"));
MyPeeps.Add(new Person("Jack"));
MyPeeps.Add(new Person("Jill"));
}
}
public class Person
{
public string Name { get; set; }
public Person(string newName)
{
Name = newName;
}
}
public class People : List<Person>
{
}
}
All the examples on the web seem to have what is effectively a static class returning code-defined data (like return new Person("blah blah")) rather than my own instance of a collection - in this case MyPeeps. Or maybe I'm not uttering the right search incantation.
One day I might make a sudden breakthrough of understanding this binding stuff but at the moment it's baffling me. Any help appreciated.

The correct way would be to use the MVVM pattern and create a ViewModel like so:
public class MainWindowViewModel : INotifyPropertyChanged
{
private People _myPeeps;
private Person _selectedPerson;
public event PropertyChangedEventHandler PropertyChanged;
public People MyPeeps
{
get { return _myPeeps; }
set
{
if (_myPeeps == value)
{
return;
}
_myPeeps = value;
RaisePropertyChanged("MyPeeps");
}
}
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
if (_selectedPerson == value)
{
return;
}
_selectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Initialize it in your View's code behind like so:
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
public MainWindow()
{
_viewModel = new MainWindowViewModel();
_viewModel.MyPeeps = new People();
_viewModel.MyPeeps.Add(new Person("Fred"));
_viewModel.MyPeeps.Add(new Person("Jack"));
_viewModel.MyPeeps.Add(new Person("Jill"));
DataContext = _viewModel;
InitializeComponent();
}
}
And bind the data like so:
<Window x:Class="WpfApplication3.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.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width="3" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<ListBox SelectedItem="{Binding SelectedPerson}"
DisplayMemberPath="Name"
ItemsSource="{Binding MyPeeps}" />
</DockPanel>
<DockPanel Grid.Column="2">
<StackPanel>
<Label>Name</Label>
<TextBox Text="{Binding SelectedPerson.Name}" />
</StackPanel>
</DockPanel>
</Grid>
</Window>
The binding will work like this:
The DataContext of the window itself is set to the ViewModel instance. Because the ListBox and the TextBox don't specify any DataContext, they inherit it from the Window. The bindings on an object always work relative to the DataContext if nothing else is being specified. That means that the TextBox binding looks for a property SelectedPerson in its DataContext (i.e., in the MainWindowViewModel) and for a Property Name in that SelectedPerson.
The basic mechanics of this sample are as follows:
The SelectedPerson property on the ViewModel is always synchronized with the SelectedItem of the ListBox and the Text property of the TextBox is always synchronized with the Name property of the SelectedPerson.

Try to inherit your People class from ObservableCollection<Person>

Related

C# WPF Databinding and Datacontext

I am (new in C# and WPF and) trying to bind data from more sources (class properties) and I am a bit confused from different guides and advices. I want to dynamically add Users in userList and showing for example the last insert and whole list at the same time. That is done on different place in source code, but simple like in contructor of my example. How and where should I set binding and datacontext for those three elements (myName,myTitle and myUserList) to reflect changes in main class properties? Should I call every time function for update binding target, or set this.datacontext after editing properties? Or should I bind to some function (if it's possible) which returns the value I need? I am a bit lost with binding to property of object and datacontexts etc. Here is an example from what I have:
<Window x:Name="Window" x:Class="WpfTest.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:WpfTest">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBox x:Name="myName" Text="" Grid.Column="1"/>
<TextBox x:Name="myTitle" Text="" Grid.Column="0"/>
</StackPanel>
<ListBox x:Name="myUserList">
</ListBox>
</Grid>
</Window>
And
public partial class MainWindow : Window {
public User myUser;
public Dictionary<int,User> userList= new Dictionary<int, User>();
public object SubWindow { get; private set; }
public MainWindow() {
newUser = new User();
newUser.Title = "John Doe";
newUser.Name = "Dr.";
this.userList.Add(index,newUser);
this.myUser=newUser;
InitializeComponent();
}
}
And
public class User
{
public String Name { get; set; }
public String Title { get; set; }
}
Thanks for any advice.
First thing is first, WPF works best when you are working with MVVM, the general idea is implementing the INotifyPropertyChanged, what ever items you add to you change, will propagate to the framework and update your views.
When working with Lists, use an ObservableCollection. If you want to add items dynamically to it, you would need to modify the ObservableCollection instead. For best results, in your UserControl, use a DataTemplate for the specific type to display a formatted version of your values.
For the second part, showing the last added item, there are a few ways you can go about this, the best would be to add a new Items(Grid, Stackpanel, etc) that can hold data, use Binding to set its value to a the same context as your list(i.e the ObservableCollection) and create a Converter that will use the ObservableCollection as input, inside your specific converter implementation, just get the last item added and Display it to the control you want( you can use a dataTemplate for this as well)
Solution: You have to bind input data in model class(User) and use model to insert data in the listbox like below
<Window x:Class="WpfRegistration.Listbox"
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:WpfRegistration"
mc:Ignorable="d"
Title="Listbox" Height="450" Width="800">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.15*"></RowDefinition>
<RowDefinition Height="0.85*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="63*"></ColumnDefinition>
<ColumnDefinition Width="26*"></ColumnDefinition>
<ColumnDefinition Width="109*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBox x:Name="myName" Text="" Grid.Column="1"/>
<TextBox x:Name="myTitle" Text="" Grid.Column="0"/>
</StackPanel>
<StackPanel>
<Button Height="23" Margin="50,40,0,0" Name="button1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="76" Click="Button1_OnClick" >Add Item</Button>
</StackPanel>
<StackPanel>
<Button Height="23" Margin="140,40,10,12" Name="DeleteButton" VerticalAlignment="Top" Click="DeleteButton_Click">Delete Item</Button>
</StackPanel>
<ListBox Grid.Row="1" Grid.Column="0" BorderThickness="3" BorderBrush="Black" Margin="0,60,0,100" x:Name="myUserList">
</ListBox>
</Grid>
</Grid>
Xaml.cs
public partial class Listbox : Window
{
public Listbox()
{
InitializeComponent();
User newUser = new User();
newUser.Title = "John Doe";
newUser.Name = "Dr.";
this.myUserList.Items.Add(newUser.Title + newUser.Name);
}
private void Button1_OnClick(object sender, RoutedEventArgs e)
{
User newUser = new User();
newUser.Title = myTitle.Text;
newUser.Name = myName.Text;
myUserList.Items.Add(newUser.Name + " " + newUser.Title );
myTitle.Text=String.Empty;
myName.Text=String.Empty;
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
myUserList.Items.RemoveAt(myUserList.Items.IndexOf(myUserList.SelectedItem));
}
}
public class User
{
public string name;
public string title;
public String Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("name");
}
}
public string Title
{
get { return title; }
set
{
title = value;
OnPropertyChanged("title");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Some confusion about MVVM data binding in a multi-tabbed interface

I am an experienced developer, but relative newcomer to the world of WPF and MVVM. I’ve been reading up on various tutorials and examples of following the MVVM pattern. I am working on converting an existing MDI Windows forms (a student/class management system) application into WPF. My basic design is for a menu (tree view) docked on the left side of the main window with a tab control that would contain the different views (student, class, teacher, billing etc) that the user requires. As proof of concept (and to get my head around WPF) I have the following:
A simple model, Student
public class Student
{
public DateTime BirthDate { get; set; }
public string Forename { get; set; }
public int Id { get; set; }
public string Surname { get; set; }
public override string ToString()
{
return String.Format("{0}, {1}", Surname, Forename);
}
}
The StudentViewModel
public class StudentViewModel : WorkspaceViewModel
{
private Student student;
public override string DisplayName
{
get
{
return String.Format("{0} {1}", student.Forename, student.Surname);
}
}
public string Forename
{
get
{
return student.Forename;
}
set
{
student.Forename = value;
RaisePropertyChanged();
RaisePropertyChanged("DisplayName");
}
}
public int Id
{
get
{
return student.Id;
}
set
{
student.Id = value;
RaisePropertyChanged();
}
}
public string Surname
{
get
{
return student.Surname;
}
set
{
student.Surname = value;
RaisePropertyChanged();
RaisePropertyChanged("DisplayName");
}
}
public StudentViewModel()
{
this.student = new Student();
}
public StudentViewModel(Student student)
{
this.student = student;
}
}
The view model inherits WorkspaceViewModel, an abstract class
public abstract class WorkspaceViewModel : ViewModelBase
{
public RelayCommand CloseCommand { get; set; }
public event EventHandler OnClose;
public WorkspaceViewModel()
{
CloseCommand = new RelayCommand(Close);
}
private void Close()
{
OnClose?.Invoke(this, EventArgs.Empty);
}
}
This in turn inherits ViewModelBase, where I implement INotifyPropertyChanged. The RelayCommand class is a standard implementation of the ICommand interface.
The MainWindowViewModel holds a collection of Workspaces
public class MainViewModel : WorkspaceViewModel
{
private WorkspaceViewModel workspace;
private ObservableCollection<WorkspaceViewModel> workspaces;
public WorkspaceViewModel Workspace
{
get
{
return workspace;
}
set
{
workspace = value;
RaisePropertyChanged();
}
}
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
return workspaces;
}
set
{
workspaces = value;
RaisePropertyChanged();
}
}
public RelayCommand NewTabCommand { get; set; }
public MainViewModel()
{
Workspaces = new ObservableCollection<WorkspaceViewModel>();
Workspaces.CollectionChanged += Workspaces_CollectionChanged;
NewTabCommand = new RelayCommand(NewTab);
}
private void NewTab()
{
Student student = new Student();
StudentViewModel workspace = new StudentViewModel(student);
Workspaces.Add(workspace);
Workspace = workspace;
}
private void Workspaces_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
{
foreach (WorkspaceViewModel workspace in e.NewItems)
{
workspace.OnClose += Workspace_OnClose; ;
}
}
if (e.OldItems != null && e.OldItems.Count != 0)
{
foreach (WorkspaceViewModel workspace in e.OldItems)
{
workspace.OnClose -= Workspace_OnClose;
}
}
}
private void Workspace_OnClose(object sender, EventArgs e)
{
var workspace = (WorkspaceViewModel)sender;
Workspaces.Remove(workspace);
}
}
The StudentView xaml
<UserControl x:Class="MvvmTest.View.StudentView"
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"
xmlns:local="clr-namespace:MvvmTest.View"
xmlns:vm="clr-namespace:MvvmTest.ViewModel"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="ID:"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Forename:"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Surname:"/>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Date of Birth:"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Id, Mode=TwoWay}"/>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Forename, Mode=TwoWay}"/>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Surname, Mode=TwoWay}"/>
<DatePicker Grid.Column="1" Grid.Row="3" SelectedDate="{Binding BirthDate, Mode=TwoWay}"/>
</Grid>
</UserControl>
The StudentViewModel and StudentView are linked via a resource dictionary in App.xaml
<ResourceDictionary>
<vm:MainViewModel x:Key="MainViewModel"/>
<DataTemplate DataType="{x:Type vm:StudentViewModel}">
<v:StudentView/>
</DataTemplate>
</ResourceDictionary>
And finally, the MainWindow view (goal is that this will eventually conform to MVVM in that the MainWindowViewModel will define the menu structure)
<Window x:Class="MvvmTest.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:MvvmTest"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:MvvmTest.ViewModel"
xmlns:v="clr-namespace:MvvmTest.View"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Vertical">
<Button Content="New Student">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding NewTabCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
<TabControl ItemsSource="{Binding Workspaces}" SelectedItem="{Binding Workspace}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"/>
<Button>X</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<UserControl Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
When I click the ‘New student’ button a new student workspace is created, added to Workspaces collection and displays in the TabControl. All seems well. But when I enter data on the view I noticed that the tab header isn’t updated. First sign that all is not working as it should...
Then when I click ‘New student’ a second time. Another workspace is created, but that duplicates the values entered in the first. Further, when editting the second tab, the first is also updated.
Placing a breakpoint into the NewTab method revealed that although the Workspaces collection holds StudentViewModels, the display properties are still null; even though the StudentView appears to hold data.
After much puzzling I discovered that if I do not set the data context on the StudentView xaml then the binding behaves properly and the test app works as expected. But then doesn't that mean the xaml designer isn't really validating the display property bindings, even though at runtime the path is resolved?
Anyway, I’m now left a few questions. How and why does what I've done work? It essentially appears to go against everything I’ve read and seen on MVVM. Furthermore when trying to apply this application to a MVVM framework (eg MVVM Light) the views are explicitly defined with the data context set in the xaml (eg: DataContext="{Binding Path=Student, Source={StaticResource Locator}}). Which makes even less sense...
As I said, what I’ve got does work, but I’m not really understanding why, and therefore doubt is clawing away that I’ve done something wrong. As a result I’m reluctant to proceed further on serious development from fear of having to rework later (having dug myself into a hole).
Child controls automatically inherit DataContext from their parent. So if no DataContext is specified in the UserControl then each instance uses the instance of StudentViewModel contained in the WorkSpaces Collection. On the other hand when specifing the datacontext in the UserControl XAML each instance of the view is bound the same ViewModel instance. That is why changing data on one view results in changes on all other views. The views are all referencing the same object. I hope that is clear.

MVVM Hierarchical Navigation tutorial StackOverFlow exception

I'm trying to reproduce a MVVM tutorial for WPF but applying it to UWP. But I've done everything in the tutorial I believe right the exact same code shown at the tutorial.
But when I ran the code I kept getting a StackOverflowException which is caused because the MainPageView keeps initializing again and again, until the exception is thrown.
The thing is I'm kinda knew at MVVM and I wish to master it, so can somebody please explain me why am I getting this?
I'll leave the code of each one of my classes and views.
This is my MainPageView.Xaml:
<Page
x:Class="MVVMHierarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo"
xmlns:views="using:MVVMHierarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHierarchiesDemo.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--Anytime the current view model is set to an instance of a CustomerListViewModel,
it will render out a CustomerListView with the ViewModel is hooked up. It’s an order ViewModel,
it'll render out OrderView and so on.
We now need a ViewModel that has a CurrentViewModel property and some logic and commanding
to be able to switch the current reference of ViewModel inside the property.-->
<Page.DataContext>
<local:MainPageView/>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="CustomerTemplate" x:DataType="viewmodel:CustomerListViewModel">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate x:Key="OrderTemplate" x:DataType="viewmodel:OrderViewModel">
<views:OrderView/>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid x:Name="NavBar"
Grid.Row="0">
<Button Content="Customers"
Command="{Binding NavCommand}"
CommandParameter="customers"
Grid.Column="0"
Grid.Row="0"/>
<Button Content="Orders"
Command="{Binding NavCommand}"
CommandParameter="orders"
Grid.Column="2"
Grid.Row="0"/>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Grid>
</Page>
This is my code-behind MainPageView.xaml.cs - here is where the StackoverflowException is thrown in the constructor it keeps calling it.
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace MVVMHierarchiesDemo
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPageView : Page
{
public MainPageView()
{
this.InitializeComponent();
}
}
}
This is my BindableBase.cs as the tutorial shows:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MVVMHierarchiesDemo
{
/*The main idea behind this class is to encapsulate the INotifyPropertyChanged implementation
* and provide helper methods to the derived class so that they can easily trigger the appropriate notifications.
* Following is the implementation of BindableBase class.*/
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName]string propertyName = null)
{
if (object.Equals(member, val))
return;
member = val;
OnPropertyChanged(propertyName);
}
}
}
This is MyCommand.cs or better known as the relay command pattern:
using System;
using System.Windows.Input;
namespace MVVMHierarchiesDemo
{
/* Now it's time to actually start doing some view switching using our CurrentViewModel property.
* We just need some way to drive the setting of this property. And we're going to make it so that
* the end user can command going to the customer list or to the order view. First add a new class
* in your project which will implement the ICommand interface. Following is the implementation of
* ICommand interface.*/
public class MyCommand<T> : ICommand
{
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyCommand(Action<T> targetExecuteMethod)
{
_TargetExecuteMethod = targetExecuteMethod;
}
public MyCommand(Action<T> targetExecuteMethod, Func<T,bool> targetCanExecuteMethod)
{
_TargetExecuteMethod = targetExecuteMethod;
_TargetCanExecuteMethod = targetCanExecuteMethod;
}
public event EventHandler CanExecuteChanged = delegate { };
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
T tparam = (T)parameter;
return _TargetCanExecuteMethod(tparam);
}
if (_TargetExecuteMethod != null)
return true;
return false;
}
void ICommand.Execute(object parameter)
{
if(_TargetExecuteMethod!=null)
{
T tparam = (T)parameter;
_TargetExecuteMethod(tparam);
}
}
}
}
This is my usercontrol for OrdersView.xaml:
<UserControl
x:Class="MVVMHierarchiesDemo.Views.OrderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<TextBlock Text="Order View"/>
</Grid>
</UserControl>
This is my user control CustomerListView.xaml:
<UserControl
x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<TextBlock Text="Customer List View"/>
</Grid>
</UserControl>
This is my OrderViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class OrderViewModel : BindableBase
{
}
}
This is my CustomerViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class CustomerListViewModel : BindableBase
{
}
}
Finally this is my MainPageViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class MainPageViewModel : BindableBase
{
public MainPageViewModel()
{
NavCommand = new MyCommand<string>(OnNavigation);
}
private CustomerListViewModel _customerListViewModel = new CustomerListViewModel();
private OrderViewModel _orderViewModel = new OrderViewModel();
private BindableBase _currentViewModel;
public BindableBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
SetProperty(ref _currentViewModel, value);
}
}
public MyCommand<string> NavCommand { get; private set; }
private void OnNavigation(string destination)
{
switch (destination)
{
case "orders":
{
CurrentViewModel = _orderViewModel;
break;
}
case "customers":
default:
CurrentViewModel = _customerListViewModel;
break;
}
}
}
}
and lastly I think the MainPageView is the one causing the infinite looping but I don't understand why?
If somebody could be so kind to tell me what I am doing wrong on UWP?
Also I could use MVVM Light or MVVMCross I'm not interested on those solutions I want to learn MVVM by hand and later on i might check those frameworks.
It's because in your MainPageView.xaml you have this:
<Page.DataContext>
<local:MainPageView/>
</Page.DataContext>
So every MainPageview creates a nested MainPageView as its DataContext. These are created until you blow the stack.
I think you meant to put a MainPageViewModel in here.

How to separate View data and ViewModel data?

I need to manage UI specific parameters (View) and Application data (Model/ViewModel) separately, so I'm using the code-behind of the View for the first, and a separated class (prefixed ViewModel) for the later. This is an simplified version of what I have:
View (XAML)
<Window x:Class="UrSimulator.View.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyView" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding FirstColumnWidth}" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Label>Width:</Label>
<TextBox Text="{Binding FirstColumnWidth}" IsReadOnly="True" Background="LightGray" />
</StackPanel>
<StackPanel Grid.Column="1">
<Label>First Column Width:</Label>
<TextBox Text="{Binding FirstColumnWidth}" />
<Label>View Model Data:</Label>
<TextBox Text="{Binding MyViewModel.PropertyFromVM}" />
<Label Content="{Binding MyViewModel.PropertyFromVM}" />
</StackPanel>
</Grid>
View (Code behind)
public partial class MyView : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private MyViewModel m_MyViewModel;
public MyViewModel MyViewModel
{
get { return m_MyViewModel; }
set
{
m_MyViewModel = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("MyViewModel"));
}
}
private GridLength m_FirstColumnWidth;
public GridLength FirstColumnWidth
{
get { return m_FirstColumnWidth; }
set
{
m_FirstColumnWidth = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("FirstColumnWidth"));
}
}
public MyView()
{
MyViewModel = new MyViewModel();
DataContext = this;
FirstColumnWidth = new GridLength(100);
InitializeComponent();
}
}
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string m_PropertyFromVM;
public string PropertyFromVM
{
get { return m_PropertyFromVM; }
set
{
m_PropertyFromVM = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("PropertyFromVM"));
}
}
public MyViewModel()
{
PropertyFromVM = "Some business data";
}
}
It works, but I find it cumbersome to use MyViewModel. on every binding that points to the VM.
Questions:
Is there another way to do this without using the prefix?
How should I write the binding for the UI (the width property) if instead of using this for the DataContext, I'd use:
DataContext = MyViewModel;
I'm doing everything wrong and this is not how it is intended to be?
Note: Forget about the converter needed for the Width, it works as long as the text is valid and is not my concern on the question.
DataContext = this;
Yuck... :)
Let the view model be the data context, and bind on your view's properties like this :
<Window x:Name="This" ...>
...
<SomeControl SomeProperty="{Binding MyViewProperty, ElementName=This}"/>
...
</Window>
Side note :
class MyView : Window, INotifyPropertyChanged
Why aren't your view's properties "dependency properties" if you inherit Window ?
This is one way of doing MVVM, but not a great choice as you are still using tightly coupled View objects.
The ideal is where you let WPF infer what View class to use by binding your ViewModel objects to the Content property of ContentPresenters and setting up DataTemplate entries for your ViewModel types.
That way, you don't even need to use a DataContext = blah statement in your code anywhere.
e.g. in the App.xaml or similar
<DataTemplate DataType={x:Type MyViewModel}>
<local:MyViewModelView/>
</DataTemplate>
... then in the Window/UserControl/XAML wherever you need it...
<ContentPresenter Content={Binding MyViewModelAsAProperty}/>
...which can be a DependencyProperty or a standard INotifyPropertyChanged enabled property on another ViewModel.
Add this codes on your MyViewModel Class
private MyView _ObjMyViewModel;
public MyView ObjMyViewModel
{
get { return _ObjMyViewModel; }
set
{
_ObjMyViewModel= value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ObjMyViewModel"));
}
}
And in XAML
<Window.DataContext>
<ViewModel:MyViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ObjMyViewModel.FirstColumnWidth}" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Label>Width:</Label>
<TextBox Text="{Binding ObjMyViewModel.FirstColumnWidth}" IsReadOnly="True" Background="LightGray" />
</StackPanel>
<StackPanel Grid.Column="1">
<Label>First Column Width:</Label>
<TextBox Text="{Binding ObjMyViewModel.FirstColumnWidth}" />
<Label>View Model Data:</Label>
<TextBox Text="{Binding MyViewModel.PropertyFromVM}" />
<Label Content="{Binding MyViewModel.PropertyFromVM}" />
</StackPanel>
I hope its working..

How to access objects in code from XAML

I am new to WPF and am trying to understand how to use data binding to bind the controls on my window to objects in my code behind. I see several questions about accessing XAML objects from the codebehind, but that's not what I'm looking for. I already know how to do that.
label1.Content = LabelText;
listbox1.ItemsSource = ListItems;
I have also seen answers about how to access a class in the codebehind from XAML.
<local:MyClass x:Key="myClass" />
But I don't see how to apply that to a specific instance of the class. Here is an example of what I'm trying to do. The 'Bindings' are obviously incorrect. That is what I need help with.
public partial class MainWindow : Window
{
private string _labelText;
private List<string> _listItems = new List<string>();
public MainWindow()
{
InitializeComponent();
_labelText = "Binding";
_listItems.Add("To");
_listItems.Add("An");
_listItems.Add("Object");
}
public string LabelText
{
get { return _labelText; }
set { _labelText = value; }
}
public List<string> ListItems
{
get { return _listItems; }
set { _listItems = value; }
}
}
<Window x:Class="SO_Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SO Demo" Height="160" Width="225">
<Grid DataContext="MainWindow">
<Label x:Name="label1" Width="80" Height="25" Margin="12,12,0,0"
Content="{Binding Path=LabelText}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<ListBox x:Name="listbox1" Width="100" Height="60" Margin="12,44,0,0"
ItemsSource="{Binding Path=ListItems}" DisplayMemberPath="ListItems"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</Window>
The books and tutorials I have read make it sound like this should be very simple. What am I missing?
While you can DataBind directly to the class in the manner you're attempting, it is not how this is commonly done. The recommended approach is to create an object (ViewModel) that aggregates all the model data you want displayed in your UI, and then set that ViewModel as the DataContext of your View (Window in this case). I would recommend reading about MVVM, which is how most WPF application are built. But the example below can get you started.
Here is a simple example based on your sample above:
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
private string _title;
private ObservableCollection<string> _items;
public string LabelText
{
get { return _title; }
set
{
_title = value;
this.RaisePropertyChanged("Title");
}
}
public ObservableCollection<string> ListItems {
get { return _items; }
set
{
_items = value; //Not the best way to populate your "items", but this is just for demonstration purposes.
this.RaisePropertyChanged("ListItems");
}
}
//Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
CodeBehind
public partial class MainWindow : Window
{
private MyViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MyViewModel();
//Initialize view model with data...
this.DataContext = _viewModel;
}
}
View (Window)
<Window x:Class="SO_Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SO Demo" Height="160" Width="225">
<Grid>
<Label x:Name="label1" Width="80" Height="25" Margin="12,12,0,0" Content="{Binding Path=LabelText}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
<ListBox x:Name="listbox1" Width="100" Height="60" Margin="12,44,0,0"
ItemsSource="{Binding Path=ListItems}"
HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
</Window>
<Grid DataContext="MainWindow"> is invalid.
If you want to reference the window you must either:
<Window x:Name="MyWindow">
<Grid DataContext="{Binding ElementName=MyWindow}"/>
</Window>
or
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"/>

Categories