Where and how do the ViewLocator-generated Views get their DataContexts assigned (in Avalonia's Todo tutorial application)? - avaloniaui

In the completed Avalonia tutorial Todo application --- where and how do TodoListView and AddItemView acquire their DataContexts to be equal to TodoListViewModel and AddItemViewModel respectively ???
I mean their parent control MainWindow's DataContext is a MainWindowViewModel instance (as assigned in App.xaml.cs file) --- while MainWindow's Content attribute is bound to MainWindowViewModel's Content property (which does get assigned instances of the viewmodels during execution). I just cannot trace the source wherefrom TodoListView and AddItemView acquire their DataContexts. Do they get it while:
Being created by the ViewLocator at runtime ? -- but that's not possible as the DataContext property of the newly instantiated View is still NULL just before the ViewLocator returns the View instance -- as shown by the output of following code in the completed application (the code in the if block is modified by me to produce required output):
public IControl Build(object data)
{
var name = data.GetType().FullName.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
{
var viewInstance = (Control)Activator.CreateInstance(type);
// the following always evaluates to true --- i.e. DataContext is always NULL before viewInstance is returned.
if(viewInstance.DataContext == null)
Console.WriteLine($"DataContext property of the newly created View instance of {name} in ViewLocator is NULL just before returning! ");
return viewInstance ;
}
else
{
return new TextBlock { Text = "Not Found: " + name };
}
}
OR they get their DataContext assigned by the parent Window ? --- but the parent window's own DataContext is MainWindowViewModel ----- how do we get to TodoListViewModel or AddItemViewModel from MainWindowViewModel ???
In short , I am unclear about where the Views generated at runtime via ViewLocator are getting their DataContext properties assigned.
Summarizing what I understood from the answer and some search and play:
If a ContentControl's (in this case Window's) Content property is a Control then the Control simply inherits the DataContext from the parent ContentControl. But if the ContentControl's Content property is a non-Control then the ContentPresenter of the ContentControl first finds a data template for the non-Control so that a child View can be generated and then sets its own DataContext to the non-Control. This newly assigned DataContext then gets inherited by the newly generated child View. Phewwwwww!

DataContext is inherited from ContentPresenter which is asking IDataTemplate.Build for a new view instance. When Content isn't a control, ContentPresenter sets its own DataContext to the value of Content which gets inherited by the child view.
ContentPresenter is used internally by the Window to display its Content property.
Same happens with ListBox items.

Related

Set the First Element to x:Null (a Xaml Question)

I created a custom Collection property for a XAML control:
class MyClass : DependencyObject
{
public ObservableCollection<object> MyCollection
{
get { return GetValue(MyCollectionProperty) as ObservableCollection<object>;}
set { SetValue(MyCollectionProperty, value); }
}
public static readonly DependecyProperty MyCollectionProperty =
DependencyProperty.Register(nameof(MyCollection), typeof(ObservableCollection<object>), new PropertyMetadata(new ObservableCollection<object>()));
}
but when I try to add elements to it in XAML:
<local:MyClass
*snip*>
<local:MyClass.MyCollection>
<x:Null/> // this causes a crash because it sets my collection to null
<Button/>
<x:Null/>
</local:MyClass.MyCollection>
</local:MyClass>
I get an XamlParseException - "...property has already been set ..." I think that what happens is the parser interprets the above code as a request to set my entire collection to null, then tries to set it to Button, then sets it to null. If Button is first then the Button and the nulls are added to my default list with no problem.
What is the correct syntax for setting the first element in a list to null in XAML? The Content Properties and Collection Syntax Combined section of XAML Syntax in Detail is the most useful reference I have found so far. My guess is that it will be some sort of Markup Extension, but everything I've tried gives the same sort of error.
One option would be to remove the setter from your MyCollection property - without an available setter, the XAML parser interprets your collection as an Implicit collection (one that can't be set, but can be added to.) This means that the parser won't interpret your initial x:Null as a null collection, and will instead add it to your existing list.
From XAML Syntax in Detail:
An implicit collection element creates a member in the logical tree representation, even though it does not appear in the markup as an element. Usually the constructor of the parent type performs the instantiation for the collection that is one of its properties, and the initially empty collection becomes part of the object tree.

Setting the initial selected item when binding to a ListView's SelectedItem property

I have a Xamarin.Forms xaml page in which I am using a ListView to allow the user to pick a single item out of a list. I have bound the ListView's SelectedItem property to a property on my ViewModel and this works fine. As soon as the user changes the selected item the property in my viewmodel updates as well.
However, even though I initially set the property in my ViewModel to one of the values from the list, when the page loads the ListView's SelectedItem property is null, which in turn sets the ViewModel property to null as well.
What I need is the other direction, I want the ListView to initially select the item that i've set in the VM property.
I can hack together a solution by writing extra code in the code behind file to explicitly set the initial selected item, but this introduces additional properties and complexity and is quite ugly.
What is the correct way to set the initial selected item of a ListView who's selected item is bound to a viewmodel property?
-EDIT-
I was asked to provide the code that I'm using for my binding.
It's very simple, standard:
<ListView x:Name="myList" ItemsSource="{Binding Documents}" SelectedItem="{Binding SelectedDocument}">
the view model that is set as the binding context for the listview is instantiated before the page is created and looks like this:
public class DocumentSelectViewModel : ViewModelBase
{
private Document selectedDocument;
public List<Document> Documents
{
get { return CachedData.DocumentList; }
}
public Document SelectedDocument
{
get { return selectedDocument; }
set { SetProperty(ref selectedDocument, value);
}
public DocumentSelectViewModel()
{
SelectedDocuement = CachedData.DocumentList.FirstOrDefault();
}
}
SetProperty is a function which simply rasies the INotifyPropertyChanged event if the new value is different from the old one, classical binding code.
I am a little rusty on XAML but don't you need to make the binding two-way?
E.G.
{ Binding SelectedDocument, Mode=TwoWay }
As long as the SelectedDocument property change raises the INotifyPropertyChanged event then you should get the desired effect.
If you replace
public DocumentSelectViewModel()
{
SelectedDocument = CachedData.DocumentList.FirstOrDefault();
}
By
public DocumentSelectViewModel()
{
SelectedDocument = Documents.FirstOrDefault();
}
Does it work for you ?
I had a similar problem that has been resolved this way...
You can use ctor DocumentSelectViewModel for set initial value. Honestly I dont like to make some job in ctor block but Xamarin.... You dont need DocumentSelectViewModel method. It will work.
public DocumentSelectViewModel ()
{
SelectedDocument = Documents[0]; //or any your desired.
}

Exposing commands for MVVM user control inside GridView in WinRT

I have a Windows Store application, following the MVVM pattern.
I have a Parent View (with matching Parent ViewModel) that contains a GridView control.
The ItemTemplate for that GridView control contains a Child View.
That child view contains a couple of buttons.
How do I wire it up such that when a user clicks a button on one of the ChildView controls, a method is called on the Parent ViewModel?
There are two methods for doing this.
first one that you can use is - bind your button to a command that is defined- in your parent viewmodel where you can do your work.
second one is - you can use mvvm messenger class. in which you have to send message from your button click eventhandler to your viewmodel. when you received this message add some eventhandler to it and perform your work there.
This is how I went about solving this problem.
Add an ICommand backed Dependency Property on the Child View code behind.
public static readonly DependencyProperty ChildButtonCommandProperty = DependencyProperty.Register("ChildButtonCommand", typeof(ICommand), typeof(ChildView),new PropertyMetadata(null, OnChildButtonCommandChanged));
public ICommand ChildButtonCommand
{
get { return (ICommand)GetValue(ChildButtonCommandProperty); }
set { SetValue(ChildButtonCommandProperty, value); }
}
private static void OnChildButtonCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var self = (ChildView)sender;
self.ChildButtonCommand.Command = (ICommand)e.NewValue;
}
In the Parent ViewModel, add a public getter property of type ICommand, implemented with a RelayCommand that you can find here: https://relaycommandrt.codeplex.com/
In the Xaml of the Parent View, bind the ChildButtonCommand in the Child View:
<GridView.ItemTemplate>
<DataTemplate>
<views:ChildView ChildButtonCommand="{Binding ElementName=ParentView, Path=DataContext.PropertyOnParentViewModel}"/>
</DataTemplate>
Examine the binding syntax closely. Since we're in a DataTemplate for the GridView Item, our DataContext is not the Parent View Model.(It's the child item objects). If we want to bind the button command to the Parent View Model, we need a reference to something in our parent view. In this case, I named the view "ParentView". Using the Binding ElementName syntax, I could bind to the DataContext of the ParentView and more specifically a property on the ParentViewModel.

passing view model from page to user control in windows store app

In a Windows Store split app, I want to pass a view model from a page to a user control. The scenario is that I want to reuse some common xaml in multiple pages, using a UserControl like a view.
In the main page:
<common:LayoutAwarePage
...
DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}"
... >
<views:MyUserControlView Model="{Binding ViewModel}" />
...
In the user control code:
public sealed partial class MyUserControlView : UserControl
{
public static readonly DependencyProperty ModelProperty =
DependencyProperty.Register("Model", typeof(MenuSource),
typeof(MyUserControlView), null);
...
public ModelType Model
{
get
{
return this.GetValue(ModelProperty) as ModelType ;
}
set
{
this.SetValue(ModelProperty, value);
}
}
The Model setter is never called. How do I hook up the user control to the parent page's view model?
Or, is there a better way to implement shared views for use in pages?
Thanks.
-John
Correct binding would be:
<views:MyUserControlView Model="{Binding}" />
You've already set DataContext for the page above. All bindings are relative to the current DataContext.
The setter still won't be called, though. It is just a wrapper to access the DependencyProperty from code. Binding will call SetValue directly.
Depending on your requirements you might not even need to define your own Model DependencyProperty. Each control automatically inherits DataContext from its parent control. In your example above the user control already has its DataContext set to the same view model as the page.

WP7/XAML: data binding to a property in the code-behind file

I'm rather new to XAML and Silverlight. I have a XAML page and a code behind class for it. In the class, I have a protected read-only property. Can I bind a control to that property? Trying to specify the root element of the XAML as the DataContext (by name, as an ElementName) causes a designer error "Value does not fall within the expected range."
EDIT: I'd like to do in the designer-fiendly way. I understand I can do everything (including control population) from code; that's not the point. Can I have the designer recognize and display the properties of my code-behind class? Not one ones from the base (PhoneApplicationPage) but the ones that I define?
Your code behind should be the datacontext.
For example on a main page code behind:
public MainPage()
{
InitializeComponent();
DataContext = this;
}
You should be able to bind to the protected property but only one way ie from the property to the xaml. As it is read-only you will not be able to get the value if it is changed on the page by the user.