How can I extend a ComboBox to support commands (MVVM)? - vb.net

As topic says, I need to extend the features of a standard Silverlight ComboBox to also support Commanding. Since I follow MVVM I need my ComboBox to communicate the SelectionChanged event to my ViewModel.
What would the code look like for doing this? I want to be able to put the Command attribute on my ComboBox XAML control.
Using (WCF RIA, MVVM, VB.NET)..
All tips appricated!

You can bind the property SelectedIndex or SelectedItem of the Combobox to your ViewModel. So you donĀ“t need any Commands.
Example (Binding to SelectedIndex):
XAML
<ComboBox SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"/>
C#
public class ComboBoxViewModel
{
private int _selectedIndex;
public int SelectedIndex {
get { return _selectedIndex; }
set {
if (value != _selectedIndex) {
_selectedIndex = value;
// Perform any logic, when the SelectedIndex changes (aka. PropertyChanged-Notification)
}
}
}
}
Example (Binding to SelectedItem):
XAML
<ComboBox SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
C#
public class ComboBoxViewModel
{
private MyViewModel _selectedItem;
public MyViewModel SelectedItem {
get { return _selectedItem; }
set {
if (value != _selectedItem) {
_selectedItem= value;
// Perform any logic, when the SelectedIndex changes ((aka. PropertyChanged-Notification)
}
}
}
}

Create a behavior that exposes an ICommand Command and a object CommandParameter. In the behavior wire up to the SelectionChanged event of your AssociatedObject. Then you can bind the command to your behavior and simulate a command for the SelectionChanged event.

Related

Xamarin.Forms (XAML): Different layouts depending on a condition

Is there a way to choose what layout initialize depending on one condition? I have a Grid for football stats but if myViewModel.Sport == Sports.Basketball I'd like to load a completely different layout.
I tried something like this with Datatrigger in each View but it seems a mess for me:
<Label Text="{Binding Goals}"
Grid.Row="1" Grid.Column="0">
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding Sport}"
Value="1">
<Setter Property="Text"
Value="{Binding Points}"/>
</DataTrigger>
</Label.Triggers>
</Label>
I show "goals" but if the Sports enum value is 1 (Sports.Basketball) I change to "points". I want to do this with lots of Labels and even Images so I need a proper way to do it.
Could someone help me? I need to load a different Grid depending on the Sport Property of my ViewModel.
Another thing you could do is place each separate sport into it's own view, add all the views to your page and set their IsVisible property depending on which sport you want to show.
An example would look like this in pseudo-code:
<Page>
<Grid>
<BasketballView IsVisible="{Binding IsBasketball}">
<SoccerView IsVisible="{Binding IsSoccer}">
<FootballView IsVisible="{Binding IsFootball}">
</Grid>
</Page>
Then set the appropriate boolean values from the ViewModel.
To use DataTemplateSelector to solve this, as mentioned by #StephaneDelcroix, you'll want a custom class that has ItemsSource and ItemTemplate properties.
I haven't thought through / tested how DataTemplateSelector would be used with this; anyone is welcome to add that to this answer.
using System.Collections;
using Xamarin.Forms;
namespace YourNamespace
{
// From https://forums.xamarin.com/discussion/19874/listview-inside-stacklayout-a-height-problem/p2, #maxx313.
public class TemplatedStack : StackLayout
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource", typeof(IList), typeof(TemplatedStack), propertyChanged: OnItemsSourceChanged);
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void OnItemsSourceChanged(BindableObject pObj, object pOldVal, object pNewVal)
{
var layout = pObj as TemplatedStack;
if (layout != null && layout.ItemTemplate != null)
{
layout.BuildLayout();
layout.ForceLayout();
}
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(TemplatedStack), propertyChanged: OnItemTemplateChanged);
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void OnItemTemplateChanged(BindableObject pObj, object pOldVal, object pNewVal)
{
var layout = pObj as TemplatedStack;
if (layout != null && layout.ItemsSource != null)
layout.BuildLayout();
}
private void BuildLayout()
{
Children.Clear();
foreach (var item in ItemsSource)
{
var view = (View)ItemTemplate.CreateContent();
view.BindingContext = item;
Children.Add(view);
}
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
return base.OnMeasure(widthConstraint, heightConstraint);
}
}
}
In your XAML, do
<yourXmlns:TemplatedStack .../>
where yourXmlns must be an xmlns declaration at top of your XAML.
Usage of ItemsSource and ItemTemplate properties is similar to how you would bind an items collection and template to a ListView.
(The reason NOT to use a ListView here, is that ListView may interfere with touch events, and adds extra layout cost.)
Bind to this a collection containing a single item.
E.g. for this question, that item would be the specific sport being viewed.

My checkbox is not binding with the member

Say we have a grid view which is binding with the data source MyInformation. One of column is a check box. I want to bind something with it.
ItemsSource="{Binding MyInformation}"
In the ViewModel.
public ObservableCollection<Container> MyInformation
{
get
{
if (this.myInformation == null)
{
this.myInformation = new ObservableCollection<Container>();
}
return this.myInformation;
}
set
{
if (this.myInformation != value)
{
this.myInformation = value;
this.OnPropertyChanged("MyInformation");
}
}
}
The class Container has a member "GoodValue".
public class Container
{
public bool GoodValue {get;set;}
//
}
I have the checkbox bind with the member.
<DataTemplate>
<CheckBox HorizontalAlignment="Center" IsChecked="{Binding GoodValue, Converter={StaticResource ShortToBooleanConverter}}" Click="CheckBox_Checked"></CheckBox>
</DataTemplate>
I don't have the property GoodValue created in ViewModel as I think GoodValue is a member of Container. The ObservableCollection includes it automatically.
The problem is each time I read the data from the database. The checkbox is unchecked. So I doubt my code. Thanks for hint.
You can do two things:
Check if there are some binding errors
Implement INotifyPropertyChanged interface into your class Container.
public class Container:INotifyPropertyChanged
{
private bool _goodValue;
public string GoodValue
{
get
{
return _goodValue;
}
set
{
_goodValue = value;
OnPropertyChanged("GoodValue");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The ObservableCollection is usefull if you want to notify to your view when a new item is inserted or deleted from the collection, but if the object contained inside it doesn't implement InotifyPropertyChanged, the changes to properties of that object won't affect any change to your view.

ComboBox SelectedValue doesn't show

I have a strange problem in my WinRT/C# XAML Metro app, using the Windows 8 Release Preview (latest patches installed). I'm using a ComboBox, whose values ItemsSource and SelectedValue are bound to properties in a ViewModel:
<ComboBox SelectedValue="{Binding MySelectedValue, Mode=TwoWay}"
ItemsSource="{Binding MyItemsSource, Mode=OneWay}"
Width="200" Height="30" />
Code behind:
public MainPage()
{
this.InitializeComponent();
DataContext = new TestViewModel();
}
And a very simple definition of the TestViewModel, using strings:
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<string> _myItemsSource = new List<string>
{
"Test Item 1",
"Test Item 2",
"Test Item 3"
};
public IEnumerable<string> MyItemsSource
{
get { return _myItemsSource; }
}
private string _mySelectedValue = "Test Item 2";
public string MySelectedValue
{
get { return _mySelectedValue; }
set
{
_mySelectedValue = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("MySelectedValue"));
}
}
}
}
Now I thought this simple solution should just work... But when I start the app, the SelectedValue="Test Item 2" doesn't show up, the ComboBox is left empty. By setting breakpoints I noticed that the bound values MyItemsSource and MySelectedValue are corectly retrieved from the View Model when I set the DataContext of the view. After this action, the ComboBox.SelectedValue property is actually set to "Test Item 2", but it just doesn't show! Also I noticed that when I change the selected value in the ComboBox by user action on the UI, the changed value shows up in the ComboBox and the View Model property is updated accordingly. So everything seems to work fine except the initial visualization of the MySelectedValue View Model property. I'm becoming really desperate about that...
Now while this is the simplest example, in the origin I wanted to bind whole entities to ComboBox, setting DisplayMemberPath and SelectedValuePath. Unfortunately, the same problem occurs.
I found the problem in my example: In the XAML markup I've defined the SelectedValue property before the ItemsSource property. If I swap both definitions in this way, it works:
<ComboBox ItemsSource="{Binding MyItemsSource, Mode=OneWay}"
SelectedValue="{Binding MySelectedValue, Mode=TwoWay}"
Width="200" Height="30" />
This is really odd and annoying. Now I would like to know: is this a bug or by design? I think this is a bug, because the control should be working regardless of the order of the defined properties in XAML.
this is working solution : you can find here https://skydrive.live.com/?cid=b55690d11b67401d&resid=B55690D11B67401D!209&id=B55690D11B67401D!209
<ComboBox Width="300" Height="32" HorizontalAlignment="Left" DisplayMemberPath="Name"
VerticalAlignment="Top" ItemsSource="{Binding PersonCollection}"
SelectedItem="{Binding SelectedPerson, Mode=TwoWay}"></ComboBox>
ViewModle class is
public class ViewModel:BaseViewModel
{
private Person selectedPerson;
public Person SelectedPerson {
get { return this.selectedPerson; }
set { this.selectedPerson = value;
this.RaisePropertyChanged("SelectedPerson");
}
}
public ObservableCollection<Person> PersonCollection { get; set; }
public ViewModel()
{
this.PersonCollection = new ObservableCollection<Person>();
this.PopulateCollection();
//setting first item as default one
this.SelectedPerson = this.PersonCollection.FirstOrDefault();
}
private void PopulateCollection()
{
this.PersonCollection.Add(new Person { Name="Oscar", Email="oscar#sl.net" });
this.PersonCollection.Add(new Person { Name = "Jay", Email = "jay#sl.net" });
this.PersonCollection.Add(new Person { Name = "Viral", Email = "viral#sl.net" });
}
}

Why does FlipView ignore SelectedItem

I'd like to use a FlipView to display some items and start showing a specific item.
For this, I have defined a view model class:
class MyDataContext
{
public MyDataContext()
{
Items = new List<MyClass>();
Items.Add(new MyClass("1"));
Items.Add(new MyClass("2"));
Items.Add(new MyClass("3"));
SelectedItem = Items[1];
}
public List<MyClass> Items { get; set; }
public MyClass SelectedItem { get; set; }
}
As you can see, the selected item is not the first item.
Now for the XAML:
<FlipView ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}"></FlipView>
However, when I run the app, the flip view shows the first item, not the second item.
Is this intentional?, or is it a bug?
Try this
<FlipView
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
your SelectedItem needs to be a TwoWay binding for it to work, since the value is set by both the control and the view model.
Was having the same issue with the FlipView and unable to get the BindableBase or the TwoWay option to work. Because the order of the list was not really a topic for me, I've created a method to reorder the ItemsSource, to start with the SelectedItem as being the first item in the Collection.
In the underlying code, the result is the new ItemsSource for the FlipView, instead of the previous List elements.
public static List<T> ReorderList(List<T> elements, T selectedElement)
{
var elementIndex = elements.FindIndex(x => x.Id == selectedElement.Id);
var result = new List<T>();
foreach (var item in elements)
{
if (elementIndex .Equals(elements.Count))
{
elementIndex = 0;
}
result.Add(elements[elementIndex]);
elementIndex++;
}
return result;
}
On top of what Filip stated, you're class (MyDataContext) needs to notify the UI that the property has changed. Your ViewModel must implement INotifyPropertyChanged and the property needs to fire the PropertyChanged event
public class ViewModel : INotifyPropertyChanged
{
private object _selectedItem;
public object SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
You can also use the BindableBase class that comes with the sample apps
public class ViewModel : BindableBase
{
private object _selectedItem;
public object SelectedItem
{
get { return this._selectedItem; }
set { this.SetProperty(ref this._selectedItem, value); }
}
}
It's look like a bug.
If you debug your code you will notice that at first your SelectedItem in VM set to the right element, then it sets to null and after that it sets to the first element of FlipView's ItemsSource collection.
As a workaround I see setting SelectedItem of VM after Loaded event of FlipView is raised.

Changing part of view at runtime

I show several movie items in an ObservableCollection using a typical listbox+datatemplate view.
However, I want, in the same page, to be able to quickly change the view to what I define a posterview (i.e. only the posterimages in a wrappanel).
The xaml-page uses a viewmodel as datacontext.
Is there a way to basically replace part of the XAML content with another?
And still keep as little code as possible in the codebehind of the view.
I've seen WPF examples that for example use a DataTrigger bound to a viewmodelproperty which is very clean,
such as this article
... but Windows Phone does not have a DataTriggers, correct?
I'm trying to go for an MVVM-ish approach, so as little code as possible in the view code-behind is required.
So I want to change this:
<ContentControl DataContext="{Binding CinemaShowsOverview }" Template="{StaticResource ListView}" />
To:
<ContentControl DataContext="{Binding CinemaShowsOverview }" Template="{StaticResource PosterView}" />
DataTemplates with a DataTemplateSelector would be the way to go around this problem.
Base Data Template Selector:
public class DataTemplateSelector : ContentControl
{
public virtual DataTemplate SelectTemplate(object item, DependencyObject container)
{
throw new NotImplementedException();
}
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
ContentTemplate = SelectTemplate(newContent, this);
}
}
Specialized Template Selector for your CinemaShowsOverview
public class CinemaShowsTemplateSelector : DataTemplateSelector
{
public DataTemplate ListTemplate
{
get;
set;
}
public DataTemplate PosterTemplate
{
get;
set;
}
public DataTemplate DefaultTemplate
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
return DefaultTemplate;
var viewModel = item as CinemaShowsOverview;
if (viewModel != null)
return viewModel.IsPoster ? PowerTemplate : ListTemplate;
else
return DefaultTemplate;
}
}
And then in XAML (replacing your current ContentControl):
<assets:CinemaShowsTemplateSelector PosterTemplate="{StaticResource PosterView}"
ListTemplate="{StaticResource ListView}"
Content="{Binding CinemaShowsOverview}">
Just to be pedantic, the blog you mention describes typed data-templates, not datatriggers (as the author class them). No, this feature is not available in Silverlight for WP7.
You could expose the template you requires as a string within your view model, i.e. a string that is either ListView or PosterView. You then bind your Template property to this view-model property via a value converter that provides the template, which it can access via your applications Resources.