Binding to selection of ViewModel - data-binding

I'm trying to bind my ListBox to a selection of my ViewModel, because I have multiple ListBoxes in a Pivot and I don't want to type out the entire Page for each property. To illustrate my issue, here's a small sample:
XAML:
<DataTemplate x:Key="PropertyTemplate">
<StackPanel>
<TextBlock Text="{Binding Label}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
<controls:Pivot>
<controls:PivotItem>
<ListBox ItemsSource="{Binding PropertySelectionOne}" ItemTemplate="{StaticResource PropertyTemplate}" />
</controls:PivotItem>
<controls:PivotItem>
<ListBox ItemsSource="{Binding PropertySelectionTwo}" ItemTemplate="{StaticResource PropertyTemplate}" />
</controls:PivotItem>
</controls:Pivot>
ViewModel:
public class SomeViewModel
{
private Property _propOne;
public Property PropOne
{
get { return _propOne; }
set { _propOne = value; NotifyPropertyChanged("PropOne"); }
}
private Property _propTwo;
public Property PropTwo
{
get { return _propTwo; }
set { _propTwo = value; NotifyPropertyChanged("PropTwo"); }
}
private Property _propThree;
public Property PropThree
{
get { return _propThree; }
set { _propThree = value; NotifyPropertyChanged("PropThree"); }
}
}
So basically I want to bind my ListBoxes to PropertySelectionOne and PropertySelectionTwo, which would contain references to a selection of the properties in my ViewModel. For instance, PropertySelectionOne could include PropOne and PropTwo and PropertySelectionTwo could include PropTwo and PropThree.
Is there a simple way to "group" these properties to a new property to bind against without changing the architecture of my application?
Thanks

If you've got different properties to be displayed from the same date type in different list boxes, then arguably you need to split your view model, but you say you don't want to change the architecture of your application, which is your choice.
So, what you need to do is to provide a different ItemTemplate for each ListBox that defines which properties and how you want to display in each ListBox. Then you can bind the ItemsSource for all of the list boxes to the same data source but they will present different properties according to the ItemTemplate.
Not sure how familiar you are with these concepts, but you know that ItemsSource needs to be a collection of your data instances (SomeViewModel?), right?

Related

Call ICommand outside item source

I have an ICommand in my PageViewModel and want it to be call in my CheckedChanged of RadioButton. However, this RadioButton is inside:
<views:RoundedPage>
<Carousel ItemSource="...">
<DataTemplate DataType="...">
<CollectionView ItemSource="...">
<DataTemplate DataType="...">
<RadioButton CheckedChanged="" />
</DataTemplate>
</CollectionView>
</DataTemplate>
</Carousel>
</views:RoundedPage>
So how am I gonna call this command outside those sources.
Thank you
At first, I don't recommend you to deal with the UI event in your view model. The view model is used to resolve the logic behavior behand the view.
So you can set the CheckedChanged event by the following method:
1.create a void method in the page.cs, such as
private void RadioButton_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
//do something
}
2.binding in the xaml
<RadioButton CheckedChanged="RadioButton_CheckedChanged"/>
If you still want to deal with the event in the viewmodel, you need to use the behavior to covert the event to a command.
There is a simple in the official document and you can check it:
https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/behaviors-eventtocommandbehavior/

Caliburn Micro Binding on Dependency Property

I have an custom control with following DP:
public FrameworkElement NoResultContent
{
get { return (FrameworkElement)GetValue(NoResultContentProperty); }
set { SetValue(NoResultContentProperty, value); }
}
public static readonly DependencyProperty NoResultContentProperty =
DependencyProperty.Register("NoResultContent", typeof(FrameworkElement), typeof(AdvancedAutoCompleteBox), new PropertyMetadata(null));
The ControlTemplate of my custom control shows this DP in a ContentControl:
<ContentControl Content="{TemplateBinding NoResultContent}" />
It's used in a view to provide arbitrary functions:
<Controls:AdvancedAutoCompleteBox
x:Name="Box"
ItemsSource="{Binding Persons}"
SelectedItem="{Binding SelectedPerson}"
Watermark="Search here">
<Controls:AdvancedAutoCompleteBox.NoResultContent>
<StackPanel>
<Button
Content="Add by ICommand"
Command="{Binding AddPerson}" />
<Button
x:Name="AddPerson"
Content="Add by Caliburn" />
</StackPanel>
</Controls:AdvancedAutoCompleteBox.NoResultContent>
</Controls:AdvancedAutoCompleteBox>
The Command-Binding to a ICommand works just fine. Buy why does it not work with Caliburn.Micro?
I also tried to attach the context to the second Button manually by cal:Bind.Model

Binding to the SelectedItem in a ListBox which is in a DataTemplate for a ContentControl

Using an MVVM approach, I have a View that contains a ListBox and also a Grid in which I want to display information about the SelectedItem in the ListBox. I want to set the DataContext for the Grid to the SelectedItem.
However, the ListBox is buried as follows: A ContentControl that is bound to a DataTemplate that is a UserControl View that contains the ListBox.
Here is the MainWindow Grid that I'm not sure how to bind:
<Grid DataContext="{Binding ElementName=MyList, ????}">
Here is the ContentControl in the same View:
<ContentControl x:Name="MyList"
Content="{Binding}"
ContentTemplate="{StaticResource MyListTemplate}"/>
Here is the Data Template in the same View:
<Window.Resources>
<DataTemplate x:Key="MyListTemplate">
<v:MyListView/>
</DataTemplate>
</Window.Resources>
Here is MyListView:
<UserControl>
<ListBox Name="MyListBox" ItemsSource="{Binding ItemList}"/>
</UserControl>
I am adding to code I wrote a couple of years ago and have been away from WPF for a while, so alas, I am rusty on my data binding. I have been trying to add a SelectedItem property to the view model for MyListView and/or the MainWindow. I expect this may require RelativeSource.
Doh! I was forgetting to specify the OnPropertyChanged call for my property.
In the UserControl ListBox, I needed this:
ItemsSource="{Binding ItemList}" SelectedItem="{Binding MySelectedItem}"
In the main window view model, I needed this:
public MyItemViewModel MySelectedItem
{
get { return _mySelectedItem; }
set
{
_mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
Then, in the main window, the binding is simply:
<Grid DataContext="{Binding MySelectedItem}">

ObservableCollection<Double[]> and XAML

I have a class that inherits ObservableCollection< Double[] > (let's call it "TestClass"). I.e. collection of double arrays. Can I use this kind of collection in XAML. I'm trying to add items but it looks like I cannot add double arrays as items. Is this even possible?
Something like this:
<TestClass>
<x:Array Type="sys:Double">
<!-- What comes here...? -->
</x:Array>
</TestClass>
Actually, I would rather like to use ObservableCollection< Double[,] > but I think it's impossible - two-dimensional array I mean.
Help me out here... :)
First, you need a ViewModel. The ViewModel will be your container class where we insert custom double-arrays or fetch them from the database. If it's not just a lookup, you will need to implement INotifyPropertyChanged (but that's a different topic):
namespace MyCompany.Common.ViewModels
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class PointsArrayVM
{
private double[] _points;
public double[] Points
{
get
{
return _points;
}
set
{
_points = value;
}
}
}
}
In this example, I'll add two custom records of double[] (firstArray & secondArray). I then assign the collection to a CollectionViewSource, and (just to illustrate) I assign more records from the database to a second CollectionViewSource with the exposed MainViewModel property, List<PointsArrayVM> DatabasePoints. If it's not just a lookup, you will need an ObservableCollection instead of a List. In your XAML, under Window.Resources, add the following:
<x:Array x:Key="firstArray" Type="sys:Double"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:Double>1.1</sys:Double>
<sys:Double>1.2</sys:Double>
<sys:Double>1.3</sys:Double>
</x:Array>
<x:Array x:Key="secondArray" Type="sys:Double"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:Double>2.1</sys:Double>
<sys:Double>2.2</sys:Double>
<sys:Double>2.3</sys:Double>
</x:Array>
<x:Array x:Key="pointsArray" Type="{x:Type viewmodels:PointsArrayVM}"
xmlns:viewmodels="clr-namespace:MyCompany.Common.ViewModels;assembly=Common">
<viewmodels:PointsArrayVM Points="{StaticResource firstArray}"/>
<viewmodels:PointsArrayVM Points="{StaticResource secondArray}"/>
</x:Array>
<CollectionViewSource x:Key="customPointsCollectionViewSource" Source="{StaticResource pointsArray}"/>
<CollectionViewSource x:Key="databasePointsCollectionViewSource" Source="{Binding DatabasePoints}"/>
Now that we have our CollectionViewSources, we can add them to a CompositeCollection with CollectionContainers. In this example I'm using Points[0] as the display text, and Points1 as the selected value:
<ComboBox Text="{Binding PointsFilter}" VerticalAlignment="Top"
SelectedValuePath="Points[0]" DisplayMemberPath="Points[1]">
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource customPointsCollectionViewSource}}"/>
<CollectionContainer Collection="{Binding Source={StaticResource databasePointsCollectionViewSource}}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
I hope this helps! For some very informative XAML tips, have a look at this site.
Regarding your second question
Yes, WPF seems to have a problem with assigning a control path to a specific point in a multi-dim array. You can however work around this by also having a Points2DArray ViewModel that contains an array of PointsArrayVM objects:
namespace MyCompany.Common.ViewModels
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class Points2DArrayVM
{
private PointsArrayVM[] _pointsArrays;
public PointsArrayVM[] PointsArrays
{
get
{
return _pointsArrays;
}
set
{
_pointsArrays = value;
}
}
}
}
So in your XAML, you can now put the collections of one ViewModel into the other container ViewModel:
<x:Array x:Key="pointsArray1" Type="{x:Type viewmodels:PointsArrayVM}"
xmlns:viewmodels="clr-namespace:MyCompany.Common.ViewModels;assembly=Common">
<viewmodels:PointsArrayVM Points="{StaticResource firstArray}"/>
<viewmodels:PointsArrayVM Points="{StaticResource secondArray}"/>
</x:Array>
<x:Array x:Key="pointsArray2" Type="{x:Type viewmodels:PointsArrayVM}"
xmlns:viewmodels="clr-namespace:MyCompany.Common.ViewModels;assembly=Common">
<viewmodels:PointsArrayVM Points="{StaticResource firstArray}"/>
<viewmodels:PointsArrayVM Points="{StaticResource secondArray}"/>
</x:Array>
<x:Array x:Key="points2DArray" Type="{x:Type viewmodels:Points2DArrayVM}"
xmlns:viewmodels="clr-namespace:MyCompany.Common.ViewModels;assembly=Common">
<viewmodels:Points2DArrayVM PointsArrays="{StaticResource pointsArray1}"/>
<viewmodels:Points2DArrayVM PointsArrays="{StaticResource pointsArray2}"/>
</x:Array>
<CollectionViewSource x:Key="customPointsCollectionViewSource" Source="{StaticResource points2DArray}"/>
Then in your ComboBox, it would be something like:
<ComboBox Text="{Binding PointsFilter}" VerticalAlignment="Top"
SelectedValuePath="PointsArrays[0].Points[0]" DisplayMemberPath="PointsArrays[0].Points[1]">
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource customPointsCollectionViewSource}}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>

Rendering a Heterogeneous Collection of View Models in Silverlight 2

I have a hierarchy of view models representing formatted content:
public abstract class ContentPartViewModel : ViewModel
{
}
public class TextContentPartViewModel : ContentPartViewModel
{
public string Text { ... }
}
public class TitleContentPartViewModel : TextContentPartViewModel
{
}
public class HyperlinkContentPartViewModel : TextContentPartViewModel
{
public string Uri { ... }
}
I have an encompassing view model that contains a collection of ContentPartViewModels to be rendered:
public class ContentViewModel
{
public ICollection<ContentPartViewModel> ContentParts { ... }
}
I then have a ContentView that renders all parts of the content:
<UserControl ...>
<ItemsControl ItemsSource="{Binding ContentParts}"/>
</UserControl>
In an ideal world, I would just define a DataTemplate for each of the content part types and they would be rendered accordingly. However, Silverlight does not support the DataType property on the DataTemplate class, so that is not an option.
Another option would be to provide a DataTemplateSelector and do the mapping from view model type to DataTemplate myself. Alas, ItemsControl in SL2 does not have an ItemTemplateSelector property - only an ItemTemplate property.
That left me with no option but to provide an ItemTemplate that then uses a converter to turn off all the UI apart from the piece relevant to that content part:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Text}" FontWeight="Bold" Visibility="{Binding Converter={StaticResource TitleContentPartConverter}}"/>
<TextBlock Text="{Binding Text}" Visibility="{Binding Converter={StaticResource TextContentPartConverter}}"/>
<HyperlinkButton Content="{Binding Text}" NavigateUri="{Binding Uri}" Visibility="{Binding Converter={StaticResource HyperlinkContentPartConverter}}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
This is obviously rather awful, both for performance and for readability/correctness of code. It also makes it much harder for me to format the output correctly. So, questions:
Can anyone recommend a better way to do this in SL2?
Can anyone confirm whether the situation has improved in SL3?
Thanks,
Kent
Yes. DataType in DataTemplate is not supported in Silverlight 2 or Silverlight 3.
You can work around ItemTemplateSelector in Silverlight. Please take a look at this sample.
http://silverlight.net/forums/t/12598.aspx
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
DataTemplateSelector selector = this.ItemTemplateSelector;
if (null != selector)
{
((ContentPresenter)element).ContentTemplate = selector.SelectTemplate(item, element);
}
}

Resources