I am trying to share a ViewModel between XAML windows. This is necessary to allow multiple views of the object instance to receive events from the ViewModel.
Specifying the ViewModel as a resource in the XAML, then overwriting it in an alternate constructor does not work. The binding will still be to the default instance created in the default constructor and will not receive events from or update the proper instance.
This does not work:
MyWindow.xaml:
<Window x:Class="MyNamespace.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace"
Title="My Window"
Width="700" Height="550">
<Window.Resources>
<local:MyViewModel x:Key="MyModel"/>
</Window.Resources>
<ContentPresenter Content="{StaticResource MyModel}"/>
</Window>
MyWindow.xaml.cs
imports ...;
namespace MyNamespace {
public partial class MyWindow {
public MyWindow() {
InitializeComponent();
}
public MyWindow(MyViewModel model)
: this() {
Resources["MyModel"] = model;
}
}
}
Nor will this:
MyWindow.xaml.cs
imports ...;
namespace MyNamespace {
public partial class MyWindow {
public MyWindow()
: this(new MyViewModel()) { }
public MyWindow(MyViewModel model) {
Resources["MyModel"] = model; // Resources not yet initialized!
InitializeComponent();
}
}
}
if you are using Microsoft.Practices.Unity you can use TransientLifetimeManager.It will make sure that only one object of your viewmodel is created.
MyThis can be done by using properties on the code-behind and using the Binding tag in the XAML rather than StaticResource as follows:
MyWindow.xaml:
<Window x:Class="MyNamespace.MyWindow"
x:Name="this"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace"
Title="My Window"
Width="700" Height="550">
<ContentPresenter Content="{Binding MyModel, ElementName=this}"/>
</Window>
MyWindow.xaml.cs
imports ...;
namespace MyNamespace {
public partial class MyWindow {
public MyViewModel MyModel { get; private set; }
public MyWindow()
: this(new MyViewModel()) { }
public MyWindow(MyViewModel model) {
MyModel = model;
InitializeComponent();
}
}
}
Multiple windows (or other components) can use the same model instance.
Edit 06-Dec-12:
The XAML was not correct and the binding would not work. Added the x:Name attribute to the root element (Window), and added the ElementName argument to the Content attribute of the bound element (ContentPresenter).
Related
I'm in the process of trying to implement "Commanding" in my Xamarin Forms app, in order to follow MVVM architecture pattern.
For Buttons, it works just fine because you can just define Command in the XAML.
But for a Picker, for example, you have to do the following
(NOTE: xct:EventToCommandBehavior used the new Xamarin Community Toolkit):
<Picker x:Name="myBackgroundColorChoicePicker" SelectedItem="{Binding BGColorChoice, Mode=TwoWay}"
x:FieldModifier="public"
TextColor="{DynamicResource TextForegroundColor}"
WidthRequest="300" HorizontalOptions="CenterAndExpand">
<Picker.Items>
<x:String>User Selected</x:String>
<x:String>Totally White</x:String>
<x:String>Totally Black</x:String>
</Picker.Items>
<Picker.Behaviors>
<xct:EventToCommandBehavior
EventName="SelectedIndexChanged"
Command="{Binding ProcessBGColorChoiceCommand}"/>
</Picker.Behaviors>
</Picker>
So, I expect that when the user changes the selected item in the Picker, that the Command ProcessBGColorChoiceCommand will be called from the ViewModel.
But, the Command in the ViewModel is NOT getting called. I set a breakpoint at the beginning of the Command code, and the breakpoint is NEVER reached.
Is there something that I'm missing? I can't get this to work.
The Binding Context is set at the top of the XAML like so:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:MedLemnMobile.ViewModels"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="MedLemnMobile.Views.ColorPage"
Title="Color"
x:FieldModifier="public"
x:Name="myColorPage"
AutomationId="myColorPage"
BackgroundColor="{DynamicResource PageBackgroundColor}"
x:DataType="viewModels:ColorViewModel">
<ContentPage.BindingContext>
<viewModels:ColorViewModel/>
</ContentPage.BindingContext>
And, the Command is defined in the ViewModel like so:
using MedLemnMobile.Classes;
using MedLemnMobile.Models;
using MedLemnMobile.Views;
using System;
using System.Globalization;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.CommunityToolkit;
namespace MedLemnMobile.ViewModels
{
public class ColorViewModel : ViewModelBase
{
public ICommand GotoMenuCommand { get; }
public ICommand ProcessBGColorChoiceCommand { get; }
public ICommand ProcessFGColorChoiceCommand { get; }
public ICommand ProcessBGRedSliderCommand { get; }
public ICommand ProcessBGGreenSliderCommand { get; }
public ICommand ProcessBGBlueSliderCommand { get; }
public ICommand ProcessFGRedSliderCommand { get; }
public ICommand ProcessFGGreenSliderCommand { get; }
public ICommand ProcessFGBlueSliderCommand { get; }
public ColorViewModel()
{
IsBGColorChoiceUserSelected = EquationLibrary.MyCurrentEqSet.BackgroundColorChoice == "User Selected";
IsFGColorChoiceUserSelected = EquationLibrary.MyCurrentEqSet.ForegroundColorChoice == "User Selected";
GotoMenuCommand = new Command<ColorPage>(GotoMenu);
ProcessBGColorChoiceCommand = new Command<ColorPage>(ProcessBGColorChoice);
ProcessFGColorChoiceCommand = new Command<ColorPage>(ProcessFGColorChoice);
ProcessBGRedSliderCommand = new Command<ColorPage>(ProcessBGRedSlider);
ProcessBGGreenSliderCommand = new Command<ColorPage>(ProcessBGGreenSlider);
ProcessBGBlueSliderCommand = new Command<ColorPage>(ProcessBGBlueSlider);
ProcessFGRedSliderCommand = new Command<ColorPage>(ProcessFGRedSlider);
ProcessFGGreenSliderCommand = new Command<ColorPage>(ProcessFGGreenSlider);
ProcessFGBlueSliderCommand = new Command<ColorPage>(ProcessFGBlueSlider);
}
The command ProcessBGColorChoiceCommand was instantiated with a parameter ColorPage.
But no command parameter binding in your xaml. To fix that, change the command definition
ProcessBGColorChoiceCommand = new Command(ProcessBGColorChoice);
//also check other commands
And get selected value from property BGColorChoice in ProcessBGColorChoice method.
I try to realize my first MVVM-Project.
First I created model called "person.cs".
Then I created a modelview "AddPerson.cs", which should dynamically creates the data, which is stored in person.cs.
In my view (completely created with xaml) I have a button which should call a method "CreatePerson()" from my "AddPerson.cs". I like to bind the method.
Additionally I have created a label which should be bound to the class "person.cs" for example to the public string "Name".
How can I set the BindingContext of the Button to the "AddPerson.cs"-class and the BindingContext of the Label to the "person.cs"-class?
Yes this is possible.
Most of the Elements inherit BindablObject. Each BindableObjaect has a BindingContext Property.
See: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-binding-basics
MainViewModel
The Viewmodel for your entire page, that holds every sub-viewmodel.
public class MainViewModel
{
public AddPersonViewModel AddPersonViewModel { get; }
public PersonViewModel PersonViewModel { get; }
public MainViewModel()
{
// the passed action is just a fake action to simulate adding a person
AddPersonViewModel = new AddPersonViewModel(value => PersonViewModel.Name = value);
PersonViewModel = new PersonViewModel();
}
}
AddPersonViewModel
Contains your add logic.
public class AddPersonViewModel : INotifyPropertyChanged
{
public AddPersonViewModel(Action<string> onAction)
{
AddPerson = new Command(() =>
{
onAction(NewName); // call your update logic
NewName = ""; // reset name
});
}
public Command AddPerson { get; }
private string _name;
public string NewName
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewName)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
PersonViewModel
Contains your "new" Person.
public class PersonViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage
Create and set your MainViewModel.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
}
MainPage.xaml
Here we bind the BindingContext of Entry and Button to the AddPersonViewModel property of our ContentPage's BindingContext which is the MainViewModel. And then we bind the Text of the Label and the Command of the Button to NewName and AddPerson properties of the local BindingContext, which is AddPersonViewModel
Same for Label.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App5"
x:Class="App5.MainPage">
<StackLayout>
<Entry BindingContext="{Binding AddPersonViewModel}" Text="{Binding NewName}"
HorizontalOptions="FillAndExpand" />
<Button BindingContext="{Binding AddPersonViewModel}" Text="Click me!" Command="{Binding AddPerson}"
HorizontalOptions="Center" />
<Label Text="Added Person:" FontAttributes="Bold"
HorizontalOptions="Center"/>
<Label BindingContext="{Binding PersonViewModel}" Text="{Binding Name}"
HorizontalOptions="Center"/>
</StackLayout>
</ContentPage>
The example is very hacky, but I think you get the point. The key is the already mentioned property BindingContext
You are missing some essential concepts which result in your requests being strange.
You don't data bind to the class definition, but to the instance of the class. As one ViewModel is a class it may contain instances of other classes that you data bind to, and everything except that is in 99% of cases a wrong thing to do and your example is not one of those 1% of cases.
So basically your ViewModel should be something like:
public class PersonViewModel
{
public Person Person {get; set}
public ICommand AddPersonCommand {get; set}
}
Your BindingContext is then an instance of PersonViewModel and then on Label you bind to Person while on the button you would bind to AddPersonCommand.
I am new to Xamarin. I have a requirement where I have to implement a ListView or say tableView that have multiple different type-size cells.
And I also have to add Header for a particular section of cells, and some of my custom cells have a horizontal scroll in it.
I have done this thing in iOS native UITableView before, but don't know how this done in Xamarin cross platform, can anyone help me out this?
You are looking for DataTemplateSelector, which is very well documented in the official Xamarin.Forms documentation.
The basics are that you create your own DataTemplateSelector class:
public class MyDataTemplateSelector : DataTemplateSelector
{
}
In that class you override OnSelectTemplate:
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
}
By checking the type of the item argument, you should be able to figure out which template to return.
So lets say you have a ViewModel for Dog and one for Cat and want to show a different DataTemplate for each of those. You would do something like:
public class DogCatTemplateSelector : DataTemplateSelector
{
public DataTemplate DogTemplate { get; set; }
public DataTemplate CatTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (item is DogViewModel)
return DogTemplate;
return CatTemplate;
}
}
Then you can consume this in your XAML:
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="dogTemplate">
<ViewCell>
... <---- define your look of dog template here
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="catTemplate">
<ViewCell>
... <---- define your look of cat template here
</ViewCell>
</DataTemplate>
<local:DogCatTemplateSelector x:Key="dogCatTemplateSelector"
DogTemplate="{StaticResource dogTemplate}"
CatTemplate="{StaticResource catTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
Then simply set the ItemTemplate to your dogCatTemplateSelector instance you've defined in the resources on your ListView:
<ListView ItemsSource="{Binding DogsCatsCollection}" ItemTemplate="{StaticResource dogCatTemplateSelector}" />
Your ViewModel would then look something like:
public class Animal : INotifyPropertyChanged
{
}
public class DogViewModel : Animal
{
}
public class CatViewModel : Animal
{
}
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Animal> DogsCatsCollection { get; }
= new ObservableCollection<Animal>();
}
Then you just populate DogsCatsCollection with instances of dogs and cats.
For example, there is a class MyUserControlBase derived from UserControl and within its constructor there is set of binding for Content dependency property.
MyUserControlBase.cs
namespace BindingBeforeInitComp
{
using System.Diagnostics;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
public class MyUserControlBase : UserControl
{
public static readonly DependencyProperty MyContentProperty = DependencyProperty.Register("MyContent",
typeof(object),
typeof(MyUserControl),
new PropertyMetadata(null, MyContentChangedCallback));
public MyUserControlBase()
{
// Set binding to the Content property.
var propertyPath = new PropertyPath("Content");
var binding = new Binding { Path = propertyPath, Source = this };
SetBinding(MyContentProperty, binding);
}
public object MyContent
{
get { return GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
private static void MyContentChangedCallback(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
Debugger.Break(); // Breaking here to show that binding to Content property has updated.
}
}
}
There is also MyUserControl(cs+xaml) derived from MyUserControlBase. In the constructor there is default IntializeComponents() method (Note: binding was set before this method in the base constructor). Before calling of IntializeComponents() the Content property is null, after calling Content is set to content described in xaml. But binding does not update a target property.
MyUserControl.xaml.cs
namespace BindingBeforeInitComp
{
using System.Diagnostics;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
public sealed partial class MyUserControl : MyUserControlBase
{
public MyUserControl()
{
Debugger.Break(); // Here Content is null.
InitializeComponent();
Debugger.Break(); // Here Content is set, but as you can see there is no binding update.
}
private void ChangeContentClick(object sender, RoutedEventArgs e)
{
// Change content by a click. In this case the property changing should trigger update.
Content = new Grid() { Background = new SolidColorBrush(Colors.Chartreuse) };
}
}
}
MyUserControl.xaml
<local:MyUserControlBase
x:Class="BindingBeforeInitComp.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BindingBeforeInitComp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid Background="OrangeRed">
<Button Click="ChangeContentClick" HorizontalAlignment="Center" Width="200" Height="150" Background="#FF00AE4F">Change content</Button>
</Grid>
</local:MyUserControlBase>
Full sources: https://github.com/inTagger/Bugs/tree/master/BindingBeforeInitComp
UPDATE: I have updated sources on the GitHub, added WPF (NET45) project to demonstrate the true way how dependency properties and bindings should work. And yes, WPF has no such problem (behavior/feature/bug).
UPDATE2: If Content dependency property is set directly in constructor - binding works, but not if Content is set somewhere inside InitializeComponents().
The InitializeComponent method's job is to load your Xaml, so it's to be expected that none of your UI elements are available until after it has run.
Consider handling the Loaded event - you can attach a handler to that in your base constructor, and it won't be raised until after the object is fully constructed.
<Window.Resource>
<ResourceDictionary>
<local:SomeResourceWithObsCollection x:Key="MyItemWithCollection">
<local:SomeClass.Instance /> <!-- THIS DOES NOT WORK -->
</local:SomeResourceWithObsCollection>
</ResourceDictionary>
</Window.Resources>
I don't know how to get that line to work... I've tried doing <x:Static SomeClass.Instance />, but that also isn't allowed.
[ContentProperty("TheItems")]
public class SomeResourceWithObsCollection
{
public class SomeResourceWithObsCollection()
{
TheItems = new ObservableCollection<IMyInterface>();
}
public ObservableCollection<IMyInterface> TheItems { get; set; }
}
public class SomeClass : IMyInterface
{
private static SomeClass _instance = new SomeClass();
private SomeClass() { }
public SomeClass Instance { get { return _instance; } }
}
You can't do what you're asking to do in XAML as of right now. Perhaps future versions of XAML will account for this. You have to do it in the code behind, here is an example:
Adding a static object to a resource dictionary
The closest I can suggest is a combination of the CompositeCollection and using ListBoxItems (or some other equivalent) to wrap your static content (as I believe you can only pull static content into XAML using the {x:Static} markup extension)
This can be used in XAML as below:
<ListBox>
<ListBox.ItemsSource>
<CompositeCollection>
<ListBoxItem Content="{x:Static local:Example.One}" />
<ListBoxItem Content="{x:Static local:Example.Two}" />
</CompositeCollection>
</ListBox.ItemsSource>
</ListBox>