WPF DataTemplate and Binding - wpf

I am DataTemplating a listbox's itemsource to display a label and combobox. In the, datatemplate I am assigning a new itemssource to the combobox but cant get it to work.
Or Ideally, how can I bind the combobox in a datatemplate to a different source.
Thanks. Mani
UserControl:
<DockPanel>
<ListBox x:Name="lstBox" ItemsSource="{Binding FilterControls}" />
</DockPanel>
<!--DataTemplate For SearchElement Type-->
<DataTemplate DataType="{x:Type CustomTypes:FilterElement}">
<Label> Text </Label>
*<ComboBox ItemsSource="{Binding Employees}"DisplayMemberPath="Sex" />*
</DataTemplate>
ViewModel:
List<FilterElement> FilterControls;
List<Employee> Employees
Class FilterElement
{
string Caption;
}
class Employee
{
string Sex;
}

In your combobox, you are binding to Employees in the current data context, which would be a FilterElement object - no Employees property to bind to.
In your binding, you probably want to set Source= to something else, which overrides your datacontext
There are lots of ways to set this one easy way to do this (easy to put here, anyway) would be to add a collectionViewSource to the resources of your window/control (I put Whatever.Resources - it can go in nearly any containing element)
<Whatever.Resources>
<CollectionViewSource x:Key="employeeSource" Source="{Binding Employees}">
</Whatever.Resources>
Then in your datatemplate
<ComboBox ItemsSource={Binding Source={StaticResource employeeSource}}" ... />
Note that using a CollectionViewSource will allow you to do sort/group in xaml as well.

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.

Binding TabControl to ObservableCollection With Different Content

'm trying to create w mainwindow that will have a tabcontrol containing multiple items ( each one is shown only on demand )... let's say we have item of type A, item of type B and item of type C
I want ( using MVVM pattern) to have an observablecollection of objects that represent my tabitems and that are related to my userscontrols ( each tabitem is a usercontrol)...
The problem is that i didn't figure out how to do it.
i've a tabItemViewModelBase class :
public class TabItemViewModelBase : ViewModelBase
{
//Fields :
RelayCommand _closeCommand;
//Constructor:
public TabViewModel(string header)
{
this.Header = header;
}
}
in my mainwindow data context, i've an observable collection of this class :
//Propriétés
ObservableCollection<TabViewModel> _tabItems;
In my MainWindow i've the following Tag for the TabControl Item
<TabControl Padding="0" ItemsSource="{Binding Path=Workspaces}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
**<view:ClientView/>**
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
but as you may see, all the items are attached to ClientView User control and i'm using tabviewitem to create my items, i need a property or a way to specify the content form for each element of the observablecollection...
( i've a ClientListingViewModel Class , and ClientCreationViewModel class) , and i can't use both because i don't know how to specify the view for each of them!
Thanks!
To specify views that target specific viewmodels, you can do this in a datatemplate.
First you need to add a namespace reference to your view and viewmodel namespaces. I've included an example using a Window, but it also works for UserControls.
<Window ...
xmlns:v="clr-namespace:PutYourViewNamespaceHere"
xmlns:vm="clr-namespace:PutYourViewModelNamespaceHere">
Next you will need to define the datatemplates in the resources section of your container.
In the example below, I'm using the ClientListingView as the datatemplate for ClientListingViewModel
<Window.Resources>
<DataTemplate DataType="{x:Type vm:ClientListingViewModel">
<v:ClientListingView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ClientCreationViewModel">
<v:ClientCreationView />
</DataTemplate>
</Window.Resources>

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).

WPF: ComboBoxes in ListBox and concurrency

I have code like this:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>Some Other Stuff Here</TextBlock>
<ComboBox ItemsSource="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The problem is, every time the outside ListBox.SelectedItem gets changed, the ComboBoxes inside it would change their SelectedIndex to -1. Which means if I click "Some Other Stuff Here" (unless the ListBoxItem it is in is selected), all the comboboxes' selection get cleared.
How do I overcome this? Thx!
Presumably your combobox is bound to something like an ObservableCollection - try exposing an instance of ICollectionView instead:
class DataSource
{
// ...
public ObservableCollection<string> MyData { get; private set; }
public ICollectionView MyDataView
{
get
{
return CollectionViewSource.GetDefaultView(this.MyData);
}
}
}
You can then bind your combobox with:
<ComboBox ItemsSource="{Binding MyDataView}" IsSynchronizedWithCurrentItem="True" />
This means that the 'selected item' for each data source is stored in the ICollectionView object instead of within the combobox, which should mean that it is persisted when the ListBox SelectedItem changes

How to bind a TextBox to plain string collection

As a part of large data model I need to display/edit a string collection defined like ObservableCollection<String>. In prototype app we use a list view to display entire of collection and a text box to edit selected element. The text box should be bound to the current element of the collection. Because GUI is subject to change I can't bind directly using <TextBox Text="{Binding SelectedItem,ElementName=listView}" />.
I tried to use
<TextBox Text="{Binding Path=/, UpdateSourceTrigger=PropertyChanged}"/>
but it works only in one direction, changing listview current item causes updating a text box but not otherwise.
How can I bind a text box directly to sting instance of current element in string collection?
Ok, so here's your ListView. I'm going to add a name to it so I can reference it elsewhere in the XAML:
<ListView
x:Name=stringList
ItemsSource="{Binding}"
SelectionMode="Single"
IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn
Header="Data Item"
Width="80"
DisplayMemberBinding="{Binding}"/>
</GridView>
</ListView.View>
</ListView>
Now in your TextBox over on the right you can bind directly to the ListView:
<TextBox Text="{Binding SelectedItem,ElementName=stringList}" />
Since your ListView is bound directly to a list of strings, SelectedItem will be the string the currently-selected ListViewItem points to.
Update
Since you're not allowed to use ElementBinding, your best bet is to introduce a ViewModel class to sit between your list and your window. Define it like this:
public class StringListViewModel : INotifyPropertyChanged
{
// you'll have to implement INotifyPropertyChanged - I won't
// do that here - do a quick search to learn how it works.
public ObservableCollection<String> List { get; set; }
private object _si;
public object SelectedItem
{
get { return _si; }
set
{
_si = value;
OnPropertyChanged("SelectedItem");
}
}
}
Now set your window's DataContext to an instance of your ViewModel class instead of pointing it directly to the string list. Bind your ListView's ItemsSource and SelectedItem to it like this:
<ListView ItemsSource="{Binding List}" SelectedItem="{Binding SelectedItem}" ... />
Now bind your TextBox to the SelectedItem of your ViewModel:
<TextBox Text="{Binding SelectedItem}" />
Now your list sets the SelectedItem on the ViewModel whenever it changes, and thus your TextBox reflects that value. Hope that makes sense.

Resources