Say I have:
<TextBlock Text="{Binding MyString}"/>
public class AlanViewModel {
public string MyString {get; set;}
public TextBlock Control { get; set; } //How to bind this to the TextBlock control?
}
Can I bind the instance of the control to the ViewModel, or must I continue to jump through hoops in the code-behind to couple them together?
I know this couples the ViewModel to the View, but it's for a good reason.
<ContentControl Content="{Binding TextBlock}" />
Related
//My Model
public class BookInfo
{
public string BookName { get; set; }
public string BookDescription { get; set; }
}
//my View Model
public ObservableCollection<BookInfo> Bookmodel { get; set; }
public BookRepoInfo()
{
Bookmodel = new ObservableCollection<BookInfo> { //**is this correct way.**
new BookInfo { BookName = "Object-Oriented Programming in C#", BookDescription = "Object-oriented programming is a programming paradigm based on the concept of objects" },
......
};
}
XAML page:
<ContentPage.BindingContext>
<local:BookRepoInfo />
</ContentPage.BindingContext>
<X:YList ItemsSource="{Binding Bookmodel}"></X:YList>
Load the list item using MVVM pattern
Assuming that YList is either a inheritance from ListView or CollectionView, you'll need to provide some sort of template which you want to apply to each cell of that list.
Right now what is happening is that it will just call the ToString() on the object that you put in.
Change your code to be something like:
<X:YList ItemsSource="{Binding Bookmodel}">
<X:YList.ItemTemplate>
<DataTemplate>
<VerticalStackLayout>
<Label Text="{Binding BookName}"/>
<Label Text="{Binding BookDescription}"/>
</VerticalStackLayout>
</DataTemplate>
</X:YList.ItemTemplate>
</X:YList>
More information is here: https://learn.microsoft.com/dotnet/maui/user-interface/controls/collectionview/populate-data?view=net-maui-7.0#define-item-appearance
I have this Model
[NotifyPropertyChanged]
public class WidgetConfiguration
{
#region Properties
#endregion Properties
}
Which i use in my ViewModel for a Collection and a Selected item property (ListView / GridView SelectedItem="{Binding SelectedWidget}" ... )
[NotifyPropertyChanged]
public class WidgetViewModel
{
public ObservableCollection<WidgetConfiguration> Configurations { get; set; } = new ObservableCollection<WidgetConfiguration>();
public WidgetConfiguration SelectedWidget { get; set; }
}
I then want to Bind SelectedWidget to a UserControl that function as editor for the SelectedItem:
<controls:WidgetConfig Widget="{Binding SelectedWidget}" />
The UserControl is defined like this (using PostSharp to declare DependencyProperties)
[NotifyPropertyChanged]
public partial class WidgetConfig : UserControl
{
[DependencyProperty]
public WidgetConfiguration Widget { get; set; }
public WidgetConfig()
{
InitializeComponent();
this.DataContext = this;
}
}
But im getting an error on the UserControl binding:
Severity Code Description Project File Line Suppression State Error A
'Binding' cannot be set on the 'Widget' property of type
'Squiddy_Client_Views_WidgetConfig_10_577403948'. A 'Binding' can only
be set on a DependencyProperty of a
DependencyObject. Client C:\develop\Squiddy\Client\Views\WidgetManager.xaml 21
I've tried implementing the DependencyProperties manually and ensured that all types was correct, even the default type and default value. it didn't help.
I've read all results on google and don't know what to do.
Is this even possible or do i need to make a proxy binding ?
EDIT:
Just for the sake of it, i tried implementing the DependencyProperty manually:
public static readonly DependencyProperty WidgetProperty =
DependencyProperty.Register("Widget", typeof(WidgetConfiguration),
typeof(WidgetConfig));
[SafeForDependencyAnalysis]
public WidgetConfiguration Widget
{
get { return GetValue(WidgetProperty) as WidgetConfiguration; }
set { SetValue(WidgetProperty, value); }
}
Now the XAML error is gone, but the binding is "dead". When selecting a new object in the ListView, the UserControl doesn't get updated:
The PropertySetter doesn't get invoked
PropertyChanged events on the ViewModel DO happen though...
EDIT 2:
I totally missed this part in the PostSharp documentation, i lacked adding the DependencyProperty along with the attribute. (thanks to Daniel Balas)
public static DependencyProperty WidgetProperty { get; private set; }
[DependencyProperty]
public WidgetConfiguration Widget { get; set; }
EDIT 3:
I finally found the answer to DataContext / root after watching this video:
https://www.youtube.com/watch?v=h7ZrdGiOm3E
I removed "this.DataContext = this" from the UserControl constructor
I added a Name="root" in the UserControl element in XAML
The bindings inside the UserControl should point to ElementName=root and use the property Widget.xxx
like this:
<UserControl Name="root">
<TextBlock Text="{Binding Header, ElementName=root}"></TextBlock>
<Label Content="{Binding Widget.Name, ElementName=root}" />
</UserControl>
The Xaml/Baml compiler determines whether the property Foo is a dependency property by looking for a FooProperty static field or property with DependencyProperty type. This field is not automatically injected by the [DependencyProperty] aspect (due to limitations of PostSharp's aspect framework).
However, when you declare this field or property it would be enough for the Xaml compiler to recognize the property as a dependency property. ( The aspect will then set the field/property at runtime, so it has a correct value at runtime and is usable. )
public static DependencyProperty WidgetProperty { get; private set; }
[DependencyProperty]
public WidgetConfiguration Widget { get; set; }
Your property setter is not invoked, because WPF bindings change the value store on the DependencyObject itself instead of accessing the property setter.
The problem seems to be in the fact that you are changing the DataContext of your control in the constructor. This is going to break Bindings set on the DataContext in the parent control (bindings use DataContext of controls they assigned to). One way to reference properties of your control is like this:
<Label x:Name="label" Content="{Binding ElementName=root, Path=Widget.Name}" HorizontalAlignment="Left" />
Where "root" is a x:Name="root" given to your control (the root UserControl element).
I have two listviews on a single Content page therefore l would like for each listview to have its own viewmodel as itemsoruce.
Adding separate ItemsSource for ListView is easier. If this is what you are looking for.
It can be done with two properties in your MainViewModel.
XAMl
<ListView
ItemsSource="{Binding ListSource1}">
</ListView>
<ListView
Grid.Column="1"
ItemsSource="{Binding ListSource2}">
</ListView>
ViewModel class
public class MainViewModel
{
public ObservableCollection<string> ListSource1 { get; set; }
public ObservableCollection<string> ListSource2 { get; set; }
public MainViewModel()
{
ListSource1 = new ObservableCollection<string>()
{
"I'm from first viewmodel",
"I'm from first viewmodel",
"I'm from first viewmodel",
"I'm from first viewmodel",
"I'm from first viewmodel",
"I'm from first viewmodel"
};
ListSource2 = new ObservableCollection<string>()
{
"I'm from second ViewModel",
"I'm from second ViewModel",
"I'm from second ViewModel",
"I'm from second ViewModel",
"I'm from second ViewModel",
"I'm from second ViewModel"
};
}
}
If you are looking to add source from different ViewModels.
ListSource1 = new ViewModel1().ListSource;
ListSource2 = new ViewModel2().ListSource;
If you wish to set BindingContext of ListView this can be done by binding the Binding Context itself as Marco suggests.
But you need to add the ViewModels as well to you main ViewModel as properties.
public ViewModel1 FirstViewModel { get; set; }
public ViewModel2 SecondViewModel { get; set; }
Xaml:
<ListView
BindingContext="{Binding FirstViewModel}"
Just set your BindingContext for your ListView.
In Xaml:
<ListView x:Name="list1">
</ListView>
<ListView x:Name="list2">
</ListView>
And in Code behind just bind it:
list1.BindingContext = ViewModel1;
list2.BindingContext = ViewModel2;
If you whish to bind it in code behind, you could use the BindingContext-Property in XAML too:
<ListView ItemSource="{Binding itemList1}" BindingContext="{Binding ViewModel1}" />
<ListView ItemSource="{Binding itemList2}" BindingContext="{Binding ViewModel2}" />
But keep in mind, that you need a "global" binding Model (or MasterViewModel) which keeps the nested View Models as child properties.
The xamarin forms class DataTemplate recieves a parameter with Type in ctor.
public class DataTemplate : ElementTemplate, IDataTemplateController
{
public DataTemplate(Type type);
...
}
How to pass the type of a specific class to a ctor in XAML? In c# I would write new DataTemplate(typeof(DeviceViewModel)). But I have to write this in XAML.
Pseudo code:
<DataTemplate>
<x:Arguments>
<typeof(viewModels:DeviceViewModel)/>
</x:Arguments>
<myControls:MyCustomControl/>
</DataTemplate>
EDIT
To make my goals more clear I created a derived example. Let's say there is following structure:
Picture
Music
Document
txt
pdf
xml
My List contains these elements. Every of this type has to be handled in own DataTemplate. In my case the items are quite complex so thats why I create a ViewModel for each Item. In code it could look like this:
public abstract class BaseViewModel
public class PictureViewModel : BaseViewModel
public class MusicViewModel : BaseViewModel
public class DocumentViewModel : BaseViewModel
/* My List full of different ViewModels*/
List<BaseViewModel> itemList;
Now I create a TemplateSelector which holds other TemplateSelector. It calls the right one based on view model type:
using System;
using Xamarin.Forms;
using MyApp.ViewModels;
namespace MyApp.TemplateSelectors
{
public class MyItemTemplateSelector : DataTemplateSelector
{
public DataTemplateSelector PictureTemplateSelector { get; set; }
public DataTemplateSelector MusicTemplateSelector { get; set; }
public DataTemplateSelector DocumentTemplateSelector { get; set; }
public DataTemplateSelector DefaultTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
switch (item)
{
case PictureViewModel vm:
return PictureTemplateSelector.SelectTemplate(item, container);
case MusicViewModel vm:
return MusicTemplateSelector.SelectTemplate(item, container);
case DocumentViewModel vm:
return DocumentTemplateSelector.SelectTemplate(item, container);
default:
return DefaultTemplate.SelectTemplate(item, container);
}
}
}
}
In XAML all the assignment happens:
<DataTemplate x:Key="PictureDefaultTemplate">
<ViewCell>
<Image Source="{Binding FilePath}"/>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="MusicDefaultTemplate">
<ViewCell>
<Button Text="Play this!"/>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="DocumentDefaultTemplate">
<ViewCell>
<Label Text="show filename at least"/>
</ViewCell>
</DataTemplate>
<!-- The DocumentTemplateSelector handles file extensions differently -->
<DataTemplate x:Key="MyTxtTemplate">
<ViewCell>
<Label Text="This is a text file"/>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="MyPdfTemplate">
<ViewCell>
<Label Text="This is a pdf file"/>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="MyXmlTemplate">
<ViewCell>
<Label Text="This is a xml File"/>
</ViewCell>
</DataTemplate>
<!--provide specific template selectors with initiated datatemplates-->
<templateSelectors:PictureTemplateSelector x:Key="pictureTemplateSelector" DefaultTemplate="{StaticResource PictureDefaultTemplate}"/>
<templateSelectors:MusicTemplateSelector x:Key="musicTemplateSelector" DefaultTemplate="{StaticResource MusicDefaultTemplate}"/>
<templateSelectors:DocumentTemplateSelector x:Key="documentTemplateSelector" DefaultTemplate="{StaticResource DocumentDefaultTemplate}"
TxtTemplate="{StaticResource MyTxtTemplate}"
PdfTemplate="{StaticResource MyPdfTemplate}"
XmlTemplate="{StaticResource MyXmlTemplate}"/>
<!--provide superior template selector with other template selectors -->
<templateSelectors:MyItemTemplateSelector x:Key="myItemTemplateSelector"
PictureTemplateSelector="{StaticResource pictureTemplateSelector}"
MusicTemplateSelector="{StaticResource musicTemplateSelector}"
DocumentTemplateSelector="{StaticResource documentTemplateSelector}"/>
Last bind MyItems (objects of type BaseViewModel) and set superior template selector. This is where I set the CachingStrategy to RecycleElementAndDataTemplate:
<ListView ItemsSource="{Binding MyItems}"
ItemTemplate="{StaticResource myItemTemplateSelector}"
IsPullToRefreshEnabled="True"
CachingStrategy="RecycleElementAndDataTemplate"/>
The Exception is thrown in DocumentTemplateSelector.
using System;
using Xamarin.Forms;
using MyApp.ViewModels;
namespace MyApp.TemplateSelectors
{
public class DocumentTemplateSelector : DataTemplateSelector
{
public DataTemplate TxtTemplate { get; set; }
public DataTemplate PdfTemplate { get; set; }
public DataTemplate XmlTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
var doc = (DocumentViewModel)item;
switch (doc.FileExtension)
{
case "txt":
return TxtTemplate; // Exception
case "pdf":
return PdfTemplate; // Exception
case "xml":
return XmlTemplate; // Exception
default:
return DefaultTemplate; // Exception
}
}
}
}
Following exception is thrown:
System.NotSupportedException: RecycleElementAndDataTemplate requires DataTemplate activated with ctor taking a type.
#IvanIčin #G.hakim I'm not worried that the default mechanism is not working but after studying the xamarin forms code from github I see no other solution to make it work without passing the type in DataTemplate ctor.
Update: new Xamarin Forms enhancement proposal
https://github.com/xamarin/Xamarin.Forms/issues/7060
Above enhancement would solve my request.
I have a xaml names Customer.xaml like this:
<Grid x:Name="customview" >
<StackPanel x:Name="CustomPanel" >
<TextBox x:Name="CustomText" />
</StackPanel>
</Grid
Using MVVM I have created ICustomerviewmodel and Customerviewmodel like this:
public interface ICustomerviewmodel
{
ICommand SaveClientCommand { get; }
}
public class Customerviewmodel : ICustomerviewmodel , INotifyPropertyChanged
{
......
private void ExecuteSaveClient()
{
//
}
My question is how I could get the value of
inside the function ExecuteSaveClient() to save this?
You should declare a string property in your view model say:
public string CustomText { get; set; }
Assign datacontext of customview to be your viewmodel int the constructor, hope this grid is in a UserControl or Window:
this.customview.DataContext = new CustomerViewModel();
Bind to that property:
<TextBox x:Name="CustomText" Text="{Binding CustomText}"/>
Implement INotifyPropertyChanged, if TwoWay binding and notification are required.
Read more into silverlight databinding here.
Use a binding expression:
<TextBox x:Name="CustomText" Text="{Binding TestProperty}" />
Where TestProperty is a public property on your view model which is the current DataContext.
If you wish to update the value in your view model, and have this reflected in the view, then the setter of the TestProperty property should invoke the PropertyChanged event on the INotifyPropertyChanged interface implemented by your view model.