How to setup UserControl - wpf

In an application I have the (simplified) task:
The application manages information about persons. The persons are stored somewhere (doesn't matter).
The user can add and remove persons to the list.
The list of persons (that is used quite often in the program) looks like this:
<UserControl>
...
<StackPanel>
<ListBox
ItemsSource="{Binding Persons}"
SelectedItem="{Binding SelectedPerson}"
SelectionMode="Single"/>
<StackPanel Orientation="Horizontal">
<Button Content="Add Person" Command="{Binding AddPersonCommand}" />
<Button Content="Remove Person" Command="{Binding RemovePersonCommand}" />
</StackPanel>
</StackPanel>
...
</UserControl>
Behind that works a ViewModel that I want to use for implementation.
Now I want wo have this control embedded into another Control/Window like this:
<personcontrol:PersonControl PersonsCollectionDP="{Binding PersonsFromMainVM}" SelectedPersonDP="{Binding SelectedPersonFromMainVM}" />
(PersonsVM and SelectedPersonVM are Properties in the VM of the UC/Window that embeds the PersonControl-UC, PersonsDP and SelectedPersonDP are DependencyProperty of the PersonControl-UC.)
I have the problem having the properties of the UC as a DependencyProperty and (at the same time) having as Properties in the UC-ViewModel.
How can I accomplish that?
UPDATE 1
Found this link where exactly my problem is discussed but still not answered. Maybe someone has a new idea...

It is all about datacontext. You could keep the bindings in usercontrol definition and later in parts where you embed your UC bind datacontext to a corresponding VM which provides your datasource of persons.
<Window>
<personcontrol:PersonControl DataContext="{Binding PersonsVM}" />
</Window>
And in VM you would have
public class WindowVM
{
public PersonsUCProviderVM PersonsVM {get;set;}
}
public class PersonsVM
{
public List<Person> Persons {get;set;}
public Person SelectedPerson {get;set;}
public ICommand AddPersonCommand {get;set;}
public ICommand RemovePersonCommand {get;set;}
}

EDIT:
usually you use elementname binding when working with usercontrol and DP.(never set the DataContext to self)
<personcontrol:PersonControl PersonsCollectionDP="{Binding PersonsFromMainVM}" SelectedPersonDP="{Binding SelectedPersonFromMainVM}" />
then your usercontrol stuff should look like this
<UserControl x:Name="uc">
<StackPanel>
<ListBox
ItemsSource="{Binding ElementName=uc, Path=PersonsCollectionDP}"
SelectedItem="{Binding ElementName=uc, Path=SelectedPersonDP}"
SelectionMode="Single"/>
</StackPanel>
</UserControl>

Related

Binding not working inside a <ItemsControl.ItemTemplate>

I can not get the binding of a text property for a DataTemplate in MVVM design pattern.
To show the problem I expose below a simplification of my problem, where I bind two different view properties to the same model property (aka AnObject.Text).
My code in MainWindow.xaml is:
...
<Button Grid.Row="0" Content="{Binding ButtonText}" />
...
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<Label Content="aaaaa" />
<TextBlock Text="{Binding ItemText}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
...
My code behind in MainWindow.xaml.cs (which sets the same DataContext for Button and every item in <ItemsControl ItemsSource>):
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
My code in MainWindowViewModel.cs is:
...
public ObservableCollection<object> MyItems => MyConverter.GetCollection(MyData.List);
public string ItemText => "dddd"; // this DOES works
public string ItemText => AnObject.Text; // this does NOT work
...
public string ButtonText => AnObject.Text; // this DOES works (note, same object property!)
...
Any idea why my binding inside the DataTemplate does not work?
Thanks in advance!
There are various things to understand here:
Button control will have the DataContext set to MainWindowViewModel instance. This is the reason why ButtonText variable value is getting reflected in Button control text.
For ItemsControl the DataContext is the the same as for the Button, i.e. the MainWindowViewModel instance.
Each item in the ItemsControl ItemsSource acts as a DataContext for the elements in the ItemTemplate, i.e. the DockPanel and its child elements. This is managed automatically by the framework. So essentially you will need a public property named ItemText in the class which will act as a DataContext for Dockpanel.
In your case the ItemText property is not the part of the objects which are in list.

Unable to get the binding from textbox and bind combobox from another view model

I am farily new to mvvm so bear with me. I have 2 View models which are inherited namely DBViewModel and PersonViewModel. i would like to add the person object in DBViewModel and bind 2 combobox with observablecollection in PersonViewModel.
public class PersonViewModel
{
private ICommand AddCommand ;
public Person PersonI{get;set;}
public ObservableCollection<Person> EmployeeList{ get; set; }
public ObservableCollection<String> OccupationList{ get; set; }
public PersonViewModel()
{
PersonI = new Person();
this.AddCommand = new DelegateCommand(this.Add);
// get OccupationList and EmployeeList
}
......
}
public class DBViewModel : PersonViewModel
{
public PersonViewModel PersonVM { get; set; }
public PersonViewModel()
{
PersonVM = new PersonViewModel();
}
....
}
<DataTemplate DataType='{x:Type viewModel:DBViewModel}'>
<StackPanel>
<TextBox Text="{Binding PersonI.Name}" />
<ComboBox Name="cboccupation" ItemsSource="{Binding OccupationList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedItem}" SelectedValuePath="Id"/>
<Button Content="Add" Command="{Binding AddCommand}" />
<DataGrid ItemsSource="{Binding EmployeeList}" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Occupation">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding OccupationList}"
DisplayMemberPath="Name" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
if you want an easier approach I'm thinking you could set a datastore field using blend and bind both controls to that field.
Your bindings are trying to bind to the properties PersonI and OccupationList on the DBViewModel, however those properties do not exist.
You need to point them to the PersonVM.PersonI and PersonVM.OccupationList instead.
<TextBox Text="{Binding PersonVM.PersonI.Name}" />
<ComboBox ItemsSource="{Binding PersonVM.OccupationList}" ... />
For your ComboBox binding inside the DataGrid, that probably will not work because the DataContext of each row in the Grid is a Person object (specified by the DataGrid.ItemsSource), and I don't think Person has a property called OccupationList.
You need to change the Source of your binding to use the object that has the OccupationList property.
For example, if your DataGrid was named MyDataGrid, the following binding for that ComboBox would work
<ComboBox ItemsSource="{Binding
ElementName=MyDataGrid,
Path=DataContext.PersonVM.OccupationList}" ... />
Alternatively, you could use a RelativeSource binding to have it look for the parent DataGrid object without needing to specify a name
<ComboBox ItemsSource="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.PersonVM.OccupationList}" ... />
As a side note, you seem to be a bit confused about bindings and the DataContext. I like to blog about beginner WPF topics, and would recommend reading What is this "DataContext" you speak of?. I find it has helped many WPF beginners on this site understand the basic binding concept. :)

Tricky WPF binding

I'm unable to do a simple but yet tricky WPF binding in Silverlight 4 (WP7 development)
I have the following code:
Class People{
public string firstname;
public string lastname;
}
Class DataSource{
public static List<People> people; // consider this as a list filled with objects already
}
I'm trying to put the list of people into a ListBox, here's the xaml I've tried:
<ListBox x:Name="peoplelistbox" Margin="0,0,-12,0" ItemsSource="{Binding DataSource.people}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding firstname}" TextWrapping="Wrap"/>
<TextBlock Text="{Binding lastname}" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But unfortunately my listbox remains empty. What am I doing wrong ?
Thank you in advance :)
Cheers,
Miloud B.
Firstly you are using fields, where you should use public property (i.e. people, firstname and lastname). Convert people to a public property, like this:
public static List<People> people { get; set; }
Then, you need to bind the ItemsSource using x:Static markup, like this:
<ListBox x:Name="peoplelistbox" Margin="0,0,-12,0">
<ListBox.ItemsSource>
<Binding Source="{x:Static local:DataSource.people}"/>
<ListBox.ItemsSource/>
...
PS: local is the xml namespace pointing to your DataSource class's namespace. Also, your class too needs to be a public class.
EDIT:
For WP7, you need to declare the instance of the class in the resources and then you can use Path to point to the source. Like this:
<phone:PhoneApplicationPage.Resources>
<local:DataSource x:Key="dataSource"/>
</phone:PhoneApplicationPage.Resources>
...
<ListBox x:Name="peoplelistbox" Margin="0,0,-12,0" ItemsSource="{Binding Source={StaticResource dataSource}, Path=people}">
PS: Again, your class needs to be public and must have a default constructor.
EDIT:
Here is a example which is working perfectly on my system. Check and see where are you making the mistake:
namespace WindowsPhoneApplication1
{
public class People
{
public string firstname { get; set; }
public string lastname { get; set; }
}
public class DataSource
{
public static List<People> people { get; set; }
public DataSource() { }
static DataSource()
{
people = new List<People> {new People {firstname = "Foo", lastname = "Bar"}};
}
}
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}
}
}
Xaml (only the relevant portions):
...
...
xmlns:local="clr-namespace:WindowsPhoneApplication1"
...
...
<phone:PhoneApplicationPage.Resources>
<local:DataSource x:Key="dataSource"/>
</phone:PhoneApplicationPage.Resources>
...
...
<ListBox x:Name="peoplelistbox" Margin="0,0,-12,0" ItemsSource="{Binding Source={StaticResource dataSource}, Path=people}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding firstname}" TextWrapping="Wrap"/>
<TextBlock Text="{Binding lastname}" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
1.FirstName and LastName need to be public properties with at least getters.
2.Your list should also be a public property unless you explicitly set the DataContext of your Window
3.Need to either set the DataContext or reference the source otherwise.
4.You cannot bind to static properties like that, use {x:Static ...}.
5.This is not a tricky binding -.-
As devdigital said you might want to implement those interfaces as well.
You can only bind to properties, so change your public fields to properties.
Also, if you want your UI to update on programmatic changes to your people instances, then implement INotifyPropertyChanged on your People type (really that type should be called Person).
If you want your UI to update when items are added/removed from your DataSource people collection, then use ObservableCollection<T> rather than List<T>.

Referencing a UIElement in a ViewModel from XAML

I'm relatively new to using WPF and the MVVM architecture. I have a question about referencing UIelements from a XAML window's DataContext.
I have menu items that are bound to Views DataContext using this syntax:
<MenuItem Header="About" Command="{Binding AboutCommand}" />
I'd like to use a similar paradigm to add items to a grid. Right now I am using a class WorkflowDesigner. I can add it to my grid using the following code in my ViewModel:
grid.AddChildren(wd.View)
where view is of type UIElement.
What I'd rather do is add is reference to it from my XAML file without putting anything in my codebehind so that I can use the XAML mostly as a skin. Is it possible to use a tag just takes its UIElement from the datacontext of the XAML file?
This is possible, but it's not within the spirit of MVVM to have your ViewModel provide controls to your view. Ideally your ViewModel should have no dependencies on System.Windows.Controls at all.
If you must, then you can use a ContentControl:
<ContentControl Content={Binding wd.View} />
To handle this I'd create a ViewLocator class and put an instance of it into your resource dictionary. Then use this:
<ContentControl Content={Binding Source={StaticResource ViewLocator}, Path=WorkflowDesigner} />
I'm not sure if I quite understand your problem, but if you have a class you wish to present in your view from your ViewModel, you could use an ItemsControl to display different classes using a DataTemplate.
Say you have class User
public class User
{
public string Id { get; set;}
public string Name { get; set;}
}
public class UserViewModel
{
private ObservableCollectionaUser<User> _users = new......
public ObservableCollection<User> Users
{
get
{
return _users;
}
}
}
In UserView, you could have
<ItemsControl ItemsSource="{Binding Users}">
<ItemsControl.Resources>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
This way, a User would be presented in the view using the template declared above. Then you would not have to use UIElements in your ViewModel.
The ItemsControl could refer to grid items, and have items presented in a grid with SharedGridScope (if I remember correctly).

How to bind silverlight datagrid combo box itemSource to viewModel

We're using Caliburn.Micro/Silverlight 4 and life is good.
I'm trying to bind a combobox's itemsSource to a viewModel, but this doesn't seem possible since the combobox is already bound to its own row's dataItem. The logic which fills the combo changes with other data on the screen so I can't really use a static list like I've been using.
Is there a way to bind directory to the viewModel somehow??? I've tried element to element binding but this never appears to work within the grid.
<Controls:DataGridTemplateColumn x:Name="FooNameCol" Header="Foo" MinWidth="200">
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Foo.ShortName}"
Style="{StaticResource DataGridTextColumnStyle}"/>
</StackPanel>
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="ShortName"
MinWidth="200" MinHeight="25"
SelectedItem="{Binding Path=Officer, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
ItemsSource="{Binding Officers, Source={StaticResource ReferenceListRetriever}}" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellEditingTemplate>
</Controls:DataGridTemplateColumn>
Within a DataTemplate, the DataContext is bound to each single item of the corresponding list; since all Bindings implicitly refers to DataContext, you have to ensure that the path is valid, starting from the single data item.
In your scenario, for the indicated binding to work, you should have a VM shaped this way:
public class MyVM {
public IEnumerable<MyItem> Items {get;}
}
public class MyItem {
public Foo Foo {get;}
public Officer Officer {get;set;}
public IEnumerable<Officer> Officers {get;}
}
It may seem an overkill, but in some scenarios each combo can actually contain different choices for each data item, based on some business rule.
In simpler cases MyItem can just expose a common list coming from the parent MyVM:
public class MyItem {
...
public IEnumerable<Officer> Officers {
get { return _parent.AvailableOfficers; }
}
}
If you really can't live with it and prefer to keep the available Officers list in the root VM only, you can use a Xaml side trick:
public class MyVM {
public IEnumerable<MyItem> Items {get;}
public IEnumerable<Officer> Officers {get;}
}
public class MyItem {
public Foo Foo {get;}
public Officer Officer {get;set;}
}
Xaml:
<UserControl ...>
...
<AnyFrameworkElementAtThisLevel Name="bridge" />
...
<Controls:WhateverGrid>
...
<Controls:DataGridTemplateColumn ...>
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
...
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="ShortName"
SelectedItem="{Binding Path=Officer, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
ItemsSource="{Binding DataContext.Officers, ElementName=bridge}" />
</DataTemplate>

Resources