Binding to a variable in view-model - wpf

This is my XAML code:
<ItemsControl MinHeight="400" BorderThickness="0" ItemsSource="{Binding People}" ItemTemplateSelector="{StaticResource myItemsTemplateSelector}" />
And in the resources:
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<Button CommandParameter="{Binding}" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Path=DataContext.SelectSomething}" >
<StackPanel>
<TextBlock Text="{Binding Cars[0].Name}"></TextBlock>
<TextBlock Text="{Binding Cars[0].Description}"></TextBlock>
</StackPanel>
</Button>
</DataTemplate>
<selectors:MySelector ItemTemplate="{StaticResource ItemTemplate}" x:Key="myItemsTemplateSelector" />
</UserControl.Resources>
People is an ObservableCollection in my view-model. Person is a class which has this property:
public IList<Cars> Cars { get; set; }
As you can see, I bind the textblocks to Cars[0].Name and Cars[0].Description. The problem I have is the 0. Instead of 0 it needs to be a variable which is defined in my view-model. How can I make the binding to be something like this:
<TextBlock Text="{Binding Cars[variableInVM].Name}"></TextBlock>

I fixed this issue by creating a new class which has these properties: Person, Cars and CurrentCars. Then, instead of binding with Cars[index], I bind with CurrentCars.

You could use an IMultiBindingConverter, and then use a MultiBinding that binds Cars and variableInVM and uses your converter.
The converter simply returns the variableInVM's element of Cars.
Inside that element, you would have the DataTemplate shown in your question, and it would use the Car passed in.

Related

How to display ObserveableCollection<T> in ListBox

I can't bind to my ObservableCollection<T> within my ListBox.
I am using MVVM, WPF.
The binding for the page works. My understanding is, the Grid (not shown in code) is bound to my DataContext, which is my ViewModel. Therefore, my ListBox can bind to my object called Folders via the Itemssource. My Folders object is very simple, it is literally
private ObservableCollection<Folders> _folders;
public ObservableCollection<Folders> Folders
{
get { return _folders; }
set
{
if (value == _folders)
return;
_folders = value;
OnPropertyChanged("Folders");
}
}
and my Folders model is
public class Folders
{
public string SourceFolder { get; set; }
public string DestinationFolder { get; set; }
}
and lastly, my XAML
<ListBox Grid.RowSpan="2" ItemsSource="{Binding Folders, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" SelectedItem="{Binding SelectedFolderItem}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}, AncestorLevel=1}, Path=DataContext}">
<StackPanel>
<TextBlock Text="{Binding SourceFolder}" />
<TextBlock Text="{Binding DestinationFolder}" />
<Button Content="Edit" Command="{Binding EditCommand}" CommandParameter="{Binding SelectedListItem}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The button binds/executes, but the 2 textblocks do not. I have also tried
<TextBlock Text="{Binding Folders.SourceFolder}" />
<TextBlock Text="{Binding Folders.DestinationFolder}" />
but it is the same issue. The content is not displayed (not binding) because if I add a watch on my ViewModel, I can see the values are as they should be.
If it helps, if I update my code to
<TextBlock Text="{Binding SelectedFolderItem.SourceFolder}" />
<TextBlock Text="{Binding SelectedFolderItem.DestinationFolder}" />
then it works although this is not desired (it just loops the correct number of times but only for the 1 item!).
Can some one point me in the right direction?
You are setting a different DataContext. You don't need that, otherwise you destroy the purpose of the item template.
<StackPanel Orientation="Horizontal">
<StackPanel>
<TextBlock Text="{Binding SourceFolder}" />
<TextBlock Text="{Binding DestinationFolder}" />
<Button Content="Edit"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.EditCommand}"
CommandParameter="{Binding}"/>
</StackPanel>
</StackPanel>
of course if you want the buttons to work aswell, you just move the relative source you currently have to the binding of the button.

Binding in listbox with textblocks not working

I have the following xaml code:
<ListBox Foreground="{Binding MyColor, Converter={local:ColorConverter}}" ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
</ListBox>
This changes the foreground color for the entire listbox, so I modified the code in this way:
<ListBox ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding MyColor, Converter={local:ColorConverter}}" Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In this way I wanted to set the foreground for an item instead for the entire listbox, but it is not working. How do I find the right datacontext ? MyColor is a property on my MainViewModel.
LATER EDIT WITH THE SOLUTION
Jens's answer was the one that showed me where I was wrong. Instead of storing simple message log strings in the ObservableCollection, I created a new class (LogItems) which contains a Message and a Color members. Now the LogCollection is typeof LogItems instead of strings.
I populate the listbox with the following code in my viewmodel:
LogItems logitem = new LogItems(myMessage, myColor);
LogCollection.Insert(0, logitem);
And the view has the following form. Also it doesn't require anymore to use RelativeSource, because the datacontext is the same.
<ListBox ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding Path=Color, Converter={local:ColorConverter}}" Text="{Binding Path=Message}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thank you all for your answers which lead me to this solution.
The DataContext of generated container in a listbox is automatically set to the corresponding item, therefore your Binding does not find the Property MyColor. You need to use a RelativeSource binding to bind to the DataContext of the containing list:
<TextBlock Foreground="{Binding DataContext.MyColor,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}},
Converter={local:ColorConverter}}"
Text="{Binding}"/>

WPF : How to create separate Resources for each item in a bound ItemsControl

I want to achieve the following:
My ViewModel exposes a property named 'Categories' which is a collection of CategoryViewModel objects
Each CategoryViewModel object exposes a property called 'Items' which is a collection of strings*.
On my View, I want a TabControl with a TabItem for each object in the 'Categories' collection.
The Content of each TabItem should be a xceed DataGrid control displaying the contents of the selected tab's Items collection.
<TabControl ItemsSource="{Binding Categories}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CategoryName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding Items}"
AutoCreateColumns="True">
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
This works ok when I bind directly to the ItemsSource property of the DataGridControl. However, in order to utilize all of the functionality of the DataGridControl, I need to bind the ItemsSource property of the DataGridControl to a DataGridCollectionViewSource object that is bound to my Items collection. I do this when the grid ISN'T nested in another control by creating a DataGridCollectionViewSource object in the Resources section of the UserControl and binding to that.
<UserControl>
<UserControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</UserControl.Resources>
<Grid>
<xcdg:DataGridControl
ItemsSource="{Binding Source={StaticResource GridData}}"
AutoCreateColumns="True">
</xcdg:DataGridControl>
</Grid>
</UserControl>
How do I need to structure the XAML so that when the TabControl is being bound, a DataGridCollectionViewSource object is created for each TabItem so that the DataGridControl that is generated within the content of the TabItem can be bound to it?
Clear as mud, right? :)
Thanks!
Notes:
*In the real solution the collection contains objects of a class that is more complex than a simple string, but a string was used to make the example more simple.
OK, this is a bit of a long-shot, but could you use the DataGrid.Tag ...
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=Tag}"
AutoCreateColumns="True">
<xcdg:DataGridControl.Tag>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</xcdg:DataGridControl.Tag>
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
Or ... resources can be defined on any FrameworkElement, so you could try:
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding Source={StaticResource GridData}}"
AutoCreateColumns="True">
<xcdg:DataGridControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</xcdg:DataGridControl.Resources>
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
I don't use the eXceed Grid so cannot test whether these work - just a couple of ideas to try!
Colin E.
You can use x:Shared="True" attribute on a resource. That means a new instance is created for every use of that resource.
Example:
<UserControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
x:Shared="False"
Source="{Binding Items}" />
</UserControl.Resources>

Binding to CurrentItem in a ItemsControl

The XAML below is basically trying to make a list of Buttons (rendered from the Name property of objects in the Views collection in the current DataContext.
When I click on a button the CurrentItem property of CollectionViewSource should change and the associated View should be displayed in a content presenter.
OK. If I click in the ListBox in the XAML below it works exactly as desired.
But, If I click a button in the UniformGrid (created by the items control) the CurrentItem property is not updated.
How do I get the CurrentItem to be updated when an item is selected in the ItemsControl?
Thanks
<UserControl x:Class="Pos.Features.Reservation.ReservationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:product="clr-namespace:Pos.Features.ProductBrowser"
xmlns:activity="clr-namespace:Pos.Features.ActivityBrowser"
xmlns:addbysku="clr-namespace:Pos.Features.AddBySku"
xmlns:client="clr-namespace:Pos.Features.ClientBrowser"
xmlns:notes="clr-namespace:Pos.Features.Notes"
xmlns:controls="clr-namespace:Pos.Views"
xmlns:res="clr-namespace:Pos.Core;assembly=Pos.Core"
Height="300" Width="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type product:ProductBrowserViewModel}">
<product:ProductBrowserView/>
</DataTemplate>
<DataTemplate DataType="{x:Type activity:ActivityBrowserViewModel}">
<activity:ActivityBrowserView/>
</DataTemplate>
<CollectionViewSource x:Name="x" x:Key="ViewsCollection" Source="{Binding Views}" />
</UserControl.Resources>
<StackPanel>
<ListBox Name="ListBoxMenu" Grid.Column="0" Margin="5" ItemsSource="{Binding Source={StaticResource ViewsCollection}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}"/>
<ItemsControl Grid.Column="2" Name="ViewList" ItemsSource="{Binding Source={StaticResource ViewsCollection}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button>
<TextBlock Text="{Binding Path=Name}" Name="txtButtonLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" Columns="{Binding Views.Count}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ContentControl Grid.Column="3" Content="{Binding Source={StaticResource ViewsCollection}, Path=CurrentItem}"/>
<Button Grid.Column="4" Click="Button_Click">dsadsd</Button>
</StackPanel>
</UserControl>
Your button does nothing. Usually your ViewModel would have an ICommand called Select (or something similar) that the Button would be bound against
Command="{Binding Select, ElementName="root"}"
and you'd pass the instance to the ICommand that you'd like to select
CommandParameter="{Binding}"
It would look something like this (c#/XAML like pseudocode):
public class MyModel { public string Name {get;set;} }
public class MyViewModel
{
public ObservableCollection<MyModel> Models {get;set;}
public ICommand Select {get;set;}
/* configure Models and Select etc */
}
<UserControl DataContext="{StaticResource MyViewModelInstance}" x:Name="root">
<ItemsControl ItemsSource="{Binding Models}">
<ItemsControl.ItemTemplate>
<ItemsPanelTemplate>
<Button Text="{Binding Name}"
Command="{Binding Select, ElementName="root"}"
CommandParameter="{Binding}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
The ItemsControl binds to Models, so each MyModel in Models gets a button. The button text is bound to the property Name. The button command is bound to the Select property in the ViewModel. When the button is pressed, it calls the ICommand, sending in the instance of MyModel that the button is bound against.
Please do note that using ViewModels within a UserControl is a code smell. UserControls should appear to users as all other controls--they should have bindable public properties which are bound to the user's ViewModel, not yours. You then bind to the values of these properties within the UserControl. For this example, you would have an ItemsSource property defined on your UserControl, and the ItemsControl would bind to this property rather than a ViewModel directly.
I think you have to do it manually in code-behind.
XAML
<Button Click="ViewListButton_Click">
<TextBlock Text="{Binding Path=Name}" Name="txtButtonLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"/>
</Button>
Code-behind
private void ViewListButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
ICollectionView view = CollectionViewSource.GetDefaultView(ViewList.ItemsSource);
view.MoveCurrentTo(button.DataContext);
}
If you're using the MVVM pattern and/or you don't want to use code-behind, you could bind the button's Command to a command in your ViewModel, as suggested by Will

M-V-VM WPF: Way to maintain Databinding while passing parent object of Databound object into IValueConverter?

I'm using model-view-viewmodel I currently have a class with 3 pieces of data: 2 integers and an enumeration.
Its constructor looks like this:
//C#
public Outcome(OutcomeEnum outcomeEnum, Int32 acutalOutcomeData, Int32 expectedOutcomeData)
{
m_outcomeEnum = outcomeEnum;
m_actualData = acutalOutcomeData;
m_expectedData = expectedOutcomeData;
}
I have 2 ComboBoxes next to each other that I have bound to one List of outcome objects (List<Outcome>) which I use to the "actual" and "expected" integer values.
This section of code looks like this: (The SelectedItem and ItemsSource are dependency properties in the viewmodel )
<ComboBox ItemsSource="{Binding Path=OutcomeList}" SelectedItem="{Binding SelectedExpectedOutcome, Mode=TwoWay}" x:Name="PART_cbExpectedOutcome" Grid.Column="1" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Path=ExpectedOutcomeData, Converter={StaticResource OutcomeDataToStringConverter}, ConverterParameter=Expected }" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox ItemsSource="{Binding Path=OutcomeList}" SelectedItem="{Binding SelectedActualOutcome, Mode=TwoWay}" x:Name="PART_cbActualOutcome" Grid.Column="2" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Path=ActualOutcomeData, Converter={StaticResource OutcomeDataToStringConverter}, ConverterParameter=Actual}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
My problem is: I'd like to pass a reference to the Outcome object into the IValueConverter that I'm using to convert between, but this appears to not be possible using the IConvertParameter but rather I would be forced to use Multibinding per the msdn post here.
I'd like to simplify my approach as creating a multibinding for something relatively as simple as this seems like overkill.
I'd like to do only one thing with the Outcome object that I seek to pass into IValueConverter, which is to determine the enumeration type of OutcomeEnum so I can provide the proper formatting of the expected or actual data value.
Is there any simpler way for me to be able to pass the Outcome object into the IValueConverter OutcomeDataToStringConverter while maintaining a two-way binding with this list of Outcome objects? I'm open to suggestions.
Why don't you bind to the entire Outcome object instead of its ActualOutcomeData or ExpectedOutcomeData properties? That way the converter will receive the entire Outcome object, and based on it and on the converter parameter return the proper value.
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Converter={StaticResource OutcomeToStringConverter}, ConverterParameter=Expected}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
For right now, I ended up changing the Outcome class so it had only one member - outcomeData
Then I created two lists of Outcome objects which were bound to the combobox's ItemsSource - ActualOutcomeList and ExpectedOutcomeList.
I can then bind the Outcome object itself to the Combobox, but by having two lists I avoid the duplicate selection issue I describe in the comment on Avaid's post.
In the end the code looks like this:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Converter={StaticResource OutcomeToStringConverter}}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox
x:Name="PART_cbActualOutcome"
Grid.Column="2"
ItemsSource="{Binding Path=ActualOutcomeList}"
SelectedItem="{Binding SelectedActualOutcome, Mode=TwoWay}"
IsEnabled="{Binding Path=IsOutcomeEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Converter={StaticResource OutcomeToStringConverter}}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Resources