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));
}
}
}
Related
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.
I am starting with MVVM pattern and I have a problem with my button's command. I have a window which contains a TextBox for login and a custom PasswordBox with Password DependencyProperty for password (I know that any password should not be kept in memory, however it is only for fun, so do not be frustrated :) ) . There is also a button and I want it to be enabled if and only if the login and password are both not empty. However my command does not work properly. Here is the code:
LoginWindow xaml:
<Window x:Class="WpfMVVMApplication1.LoginWindow"
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:WpfMVVMApplication1"
xmlns:vm="clr-namespace:WpfMVVMApplication1.ViewModel"
mc:Ignorable="d"
Title="Login" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
Width="250" Height="150">
<Window.DataContext>
<vm:LoginViewModel />
</Window.DataContext>
<Window.Resources>
<!-- here some styles for controls -->
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Login:"/>
<TextBlock Text="Password:" Grid.Row="1"/>
<TextBox Text="{Binding Login, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<local:MyPasswordBox Grid.Row="1" PasswordText="{Binding Password, Mode=TwoWay}"/>
<Button Command="{Binding SaveUserCommand}"/>
</Grid>
My LoginViewModel class
public class LoginViewModel : INotifyPropertyChanged
{
public LoginViewModel()
{
SaveUserCommand = new Command(x => SaveUser(), x => { return !string.IsNullOrEmpty(Login) && !string.IsNullOrEmpty(Password); });
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand SaveUserCommand { get; private set; }
private string login;
private string password;
public string Login
{
get
{
return login;
}
set
{
login = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Login"));
}
}
public string Password
{
get
{
return password;
}
set
{
password = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Password"));
}
}
public void SaveUser() { }
}
Also MyPasswordBox class may be useful:
<UserControl x:Class="WpfMVVMApplication1.MyPasswordBox"
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:WpfMVVMApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<PasswordBox x:Name="password" PasswordChanged="password_PasswordChanged"/>
</Grid>
public partial class MyPasswordBox : UserControl
{
public MyPasswordBox()
{
InitializeComponent();
PasswordText = "";
}
public static readonly DependencyProperty PasswordTextProperty =
DependencyProperty.Register("PasswordText", typeof(string), typeof(MyPasswordBox), new FrameworkPropertyMetadata(""));
public string PasswordText
{
get { return (string)GetValue(PasswordTextProperty); }
set { SetValue(PasswordTextProperty, value); }
}
private void password_PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordText = password.Password;
}
}
I wrote some unit tests for that which checks the result of SaveUserCommand CanExecute method and they all passed. Furthermore, I run the debug in Visual Studio adding breakpoints in setters of Login and Password properties and they both are set properly.
I have run out of ideas what I could make wrong. Can anybody help me?
I feel that I don't notify the command about the changes properly, however I do not know how to do that
In order for WPF to pick up a change to whether a command can be executed, the command's CanExecuteChanged event needs to be fired.
You will need to fire this event when the login or the password changes. You haven't shown the source of your class Command, but it will need to have a method that fires its CanExecuteChanged event. Then, just call this method from the Login and Password property setters.
I'm still pretty new to WPF and I decided to change the application I am developing to start following the MVVM pattern as best as I could. I am running into a problem when I try to have a list box dictate the view model of a content control. I've been stuck on this for a while and searching the internet is not producing answers for me.
For some reason a new instance of the view model the list box contains is being generated as the data context of the content control. When I was debugging I made sure that the list box contains the view models it should, and that the item I select on the list box is indeed the item that the list box is selecting, however the content control changing based on the selection. There is a view model populating the content control, however it is not in the collection the list box populates from. And I can somehow delete the view model in the content control via my remove button. But when I make a selection change on the list box, or add a new item to the collection it populates the content control with a new view model that once again is not in the collection. I have no clue why it is doing this, or what in my code would suggest this behavior.
I made a simple application to try and figure out what I'm doing wrong. It replicates my problem perfectly. I'm pretty sure the buttons don't adhere to MVVVM (supposed to run a command contained in the view model to adhere to MVVM from what I've been reading) but that is not my main concern right now as the problem exists without the buttons.
MainWindow.xml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="440" Width="436">
<Window.DataContext>
<local:mwvm/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ucvm}">
<local:uc/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button Content="a" HorizontalAlignment="Left" Margin="323,351,0,0" VerticalAlignment="Top" Width="95" Click="Button_Click"/>
<Button Content="r" HorizontalAlignment="Left" Margin="323,378,0,0" VerticalAlignment="Top" Width="95" Click="Button_Click_1"/>
<ContentControl Margin="10,10,110,10" Content="{Binding SelectedItem, ElementName=lb_UCs}"/>
<ListBox x:Name="lb_UCs" HorizontalAlignment="Left" Height="336" Margin="323,10,0,0" VerticalAlignment="Top" Width="95" ItemsSource="{Binding UCs}" DisplayMemberPath="CoolText"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class PanelPartsView : UserControl
{
private PanelPartsViewModel _DC;
public PanelPartsView()
{
InitializeComponent();
_DC = DataContext as PanelPartsViewModel;
}
private void btn_Remove_Click(object sender, RoutedEventArgs e)
{
_DC.Panels.Remove(lb_Panels.SelectedItem as PartsViewModel);
}
private void btn_Add_Click(object sender, RoutedEventArgs e)
{
var pvm = new PartsViewModel();
_DC.Panels.Add(pvm);
lb_Panels.SelectedItem = pvm;
System.Console.WriteLine("lb_Panels.selecteditem = {0}", ((PartsViewModel)lb_Panels.SelectedItem).PanelName);
System.Console.WriteLine("cc_PanelParts.content = {0}", ((PartsViewModel)cc_PanelParts.Content).PanelName);
}
}
mwvm
class mwvm
{
private ObservableCollection<ucvm> _UCs = new ObservableCollection<ucvm>();
public ObservableCollection<ucvm> UCs
{
get { return _UCs; }
}
public mwvm()
{
//this is for for testing, the real application would be purely dynamic
_UCs.Add(new ucvm());
_UCs.Add(new ucvm());
_UCs.Add(new ucvm());
}
}
uc.xaml
<UserControl
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:WpfApplication1" x:Class="WpfApplication1.uc"
mc:Ignorable="d" d:DesignWidth="300" Height="90">
<Grid>
<Grid.DataContext>
<local:ucvm/>
</Grid.DataContext>
<Button Content="{Binding CoolText}" Margin="10,10,10,0" Height="44" VerticalAlignment="Top"/>
<TextBox Height="23" Margin="10,59,10,0" TextWrapping="Wrap" Text="{Binding CoolText}" VerticalAlignment="Top"/>
</Grid>
</UserControl>
uc.xaml.cs
public partial class uc : UserControl
{
public uc()
{
InitializeComponent();
}
}
ucvm.cs
class ucvm : INotifyPropertyChanged
{
private static int i = 1;
private string _CoolText = "<" + i++ + ">" + System.DateTime.Now.ToLongTimeString();
public string CoolText
{
get { return _CoolText; }
set
{
_CoolText = value;
NPC("CoolText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NPC(string s)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(s));
}
}
I have also tried binding the content control like so...
<ContentControl Content="{Binding SelectedUCVMl, Mode=OneWay}"/>
<ListBox x:Name="lb_UCs" ItemsSource="{Binding UCs}" SelectedItem="{Binding SelectedUCVM}" DisplayMemberPath="CoolText"/>
...and so...
<ContentControl Content="{Binding UCs/}"/>
<ListBox x:Name="lb_UCs" ItemsSource="{Binding UCs}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="CoolText"/>
but to no avail.
Any help would be greatly appreciated.
It looks like you just have to remove this part from uc.xaml:
<Grid.DataContext>
<local:ucvm/>
</Grid.DataContext>
This syntax creates a new instance of the view model, each time an instance of uc.xaml is created, which of course isn't what you want. You want the data context of uc.xaml instances to inherit the instance currently selected in the list box.
I'm developing an app in WPF and I need to change in runtime a content of a ContentControl depending than the user selected on ComboBox.
I have two UserControls and at my combo exists two itens, corresponding each one each.
First usercontrol:
<UserControl x:Class="Validator.RespView"
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="167" d:DesignWidth="366" Name="Resp">
<Grid>
<CheckBox Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="12,12,0,0" Name="checkBox1" VerticalAlignment="Top" />
<ListBox Height="112" HorizontalAlignment="Left" Margin="12,43,0,0" Name="listBox1" VerticalAlignment="Top" Width="168" />
<Calendar Height="170" HorizontalAlignment="Left" Margin="186,0,0,0" Name="calendar1" VerticalAlignment="Top" Width="180" />
</Grid>
Second usercontrol:
<UserControl x:Class="Validator.DownloadView"
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="76" d:DesignWidth="354" Name="Download">
<Grid>
<Label Content="States" Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,35,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" />
<RadioButton Content="Last 48 hs" Height="16" HorizontalAlignment="Left" Margin="230,42,0,0" Name="rdbLast48" VerticalAlignment="Top" />
<Label Content="Kind:" Height="28" HorizontalAlignment="Left" Margin="164,12,0,0" Name="label2" VerticalAlignment="Top" />
<RadioButton Content="General" Height="16" HorizontalAlignment="Left" Margin="165,42,0,0" Name="rdbGeral" VerticalAlignment="Top" />
</Grid>
At MainWindowView.xaml
<Window x:Class="Validator.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:du="clr-namespace:Validator.Download"
xmlns:resp="clr-namespace:Validator.Resp"
Title="Validator" Height="452" Width="668"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
<Window.Resources>
<DataTemplate DataType="{x:Type du:DownloadViewModel}">
<du:DownloadView/>
</DataTemplate>
<DataTemplate DataType="{x:Type resp:RespViewModel}">
<resp:RespView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Path=PagesName}"
SelectedValue="{Binding Path=CurrentPageName}"
HorizontalAlignment="Left" Margin="251,93,0,0"
Name="cmbType"
Width="187" VerticalAlignment="Top" Height="22"
SelectionChanged="cmbType_SelectionChanged_1" />
<ContentControl Content="{Binding CurrentPageViewModel}" Height="171" HorizontalAlignment="Left" Margin="251,121,0,0" Name="contentControl1" VerticalAlignment="Top" Width="383" />
</Grid>
</Window>
I assigned to the DataContext of the MainView, the viewmodel below:
public class MainWindowViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private ViewModelBase _currentPageViewModel;
private ObservableCollection<ViewModelBase> _pagesViewModel = new ObservableCollection<ViewModelBase>();
private readonly ObservableCollection<string> _pagesName = new ObservableCollection<string>();
private string _currentPageName = "";
#endregion
public MainWindowViewModel()
{
this.LoadUserControls();
_pagesName.Add("Download");
_pagesName.Add("Resp");
}
private void LoadUserControls()
{
Type type = this.GetType();
Assembly assembly = type.Assembly;
UserControl reso = (UserControl)assembly.CreateInstance("Validator.RespView");
UserControl download = (UserControl)assembly.CreateInstance("Validator.DownloadView");
_pagesViewModel.Add(new DownloadViewModel());
_pagesViewModel.Add(new RespViewModel());
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return _changePageCommand;
}
}
public ObservableCollection<string> PagesName
{
get { return _pagesName; }
}
public string CurrentPageName
{
get
{
return _currentPageName;
}
set
{
if (_currentPageName != value)
{
_currentPageName = value;
OnPropertyChanged("CurrentPageName");
}
}
}
public ViewModelBase CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
int indexCurrentView = _pagesViewModel.IndexOf(CurrentPageViewModel);
indexCurrentView = (indexCurrentView == (_pagesViewModel.Count - 1)) ? 0 : indexCurrentView + 1;
CurrentPageViewModel = _pagesViewModel[indexCurrentView];
}
#endregion
}
On MainWindowView.xaml.cs, I wrote this event to do the effective change:
private void cmbType_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
MainWindowViewModel element = this.DataContext as MainWindowViewModel;
if (element != null)
{
ICommand command = element.ChangePageCommand;
command.Execute(null);
}
}
The app run ok and I inspected the application with WPFInspector and saw that the view changes when the combobox is changed internally, but the ContentControl still empty visually..
Sorry about the amount of code that I posted and my miss of knowledge but I'm working with this a long time and can't solve this problem.
Thanks
Issues:
Firstly don't ever create View related stuff in the ViewModel (UserControl). This is no longer MVVM when you do that.
Derive ViewModels from ViewModelBase and not ObservableObject unless you have a compelling reason to not use ViewModelBase when using MVVMLight. Keep ObservableObject inheritence for Models. Serves as a nice separation between VM's and M's
Next you do not need to make everything an ObservableCollection<T> like your _pagesViewModel. You do not have that bound to anything in your View's so it's just a waste. Just keep that as a private List or array. Check what a type actually does in difference to a similar other one.
Not sure about this one, maybe you pulled this code snippet as a demo, but do not use margins to separate items in a Grid. Your Layout is essentially just 1 Grid cell and the margins have the items not overlap. If you're not aware of that issue, Check into WPF Layout Articles.
Please don't forget principles of OOP, Encapsulation and sorts when writing a UI app. When having Properties like CurrentPageViewModel which you don't intend the View to switch make the property setter private to enforce that.
Don't resort to code-behind in the View too soon. Firstly check if it's only a View related concern before doing so. Am talking about your ComboBox SelectionChanged event handler. Your purpose of that in this demo is to switch the Bound ViewModel which is held in the VM. Hence it's not something that the View is solely responsible for. Thus look for a VM involved approach.
Solution:
You can get a working example of your code with the fixes for above from Here and try it out yourself.
Points 1 -> 5 are just basic straightforward changes.
For 6, I've created a SelectedVMIndex property in the MainViewModel which is bound to the SelectedIndex of the ComboBox. Thus when the selected index flips, the property setter after updating itself updates the CurrentPageViewModel as well such as
public int SelectedVMIndex {
get {
return _selectedVMIndex;
}
set {
if (_selectedVMIndex == value) {
return;
}
_selectedVMIndex = value;
RaisePropertyChanged(() => SelectedVMIndex);
CurrentPageViewModel = _pagesViewModel[_selectedVMIndex];
}
}
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>