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.
Related
I have a simple model:
public sealed partial class ResultsModel : ObservableObject {
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
[NotifyCanExecuteChangedFor(nameof(ClearCommand))]
[ObservableProperty]
ObservableCollection<Arrivals> _arrivals = new();
public RelayCommand SaveCommand { get; private set; }
public RelayCommand ClearCommand { get; private set; }
internal ResultsModel() {
SaveCommand = new RelayCommand(SaveRequest, CanSaveClear);
ClearCommand = new RelayCommand(OnClear, CanSaveClear);
}
public bool CanSaveClear() {
return _arrivals.Count > 0;
}
void OnClear() {
_arrivals.Clear();
}
async void SaveRequest() {
// save stuff
}
}
// c#
DataContext = (model = new ResultsModel());
...
model.Arrivals.insert(0, thing);
// The _arrivals are bound to an ItemsRepeater and appear in gui as //they're added
<ItemsRepeater ItemsSource="{Binding Arrivals}">
<Button Content="Clear" Command="{Binding ClearCommand}"/>
<Button Content="Save" Command="{Binding SaveCommand}" />
I've bound buttons to the two Commands and they work ok, I just can't work how to get the canExecute code to run more than one time.
I was expecting that when items get added to the _arrivals collection (and they do) the canExecutes would be re-evaluated via the NotifyCanExecuteChangedFor attribute, but I'm obviously missing some glue somewhere because the button are always disabled.
Any help would be appreciated.
It won't happen when you added an item to the Arrivals. But it will happen when you change the Arrivals by giving a new ObservableCollection. You could create a simple string property to test this behavior.
The reason for this behavior is that when the NotifyCanExecuteChangedFor Attribute is used, the IRelayCommand.NotifyCanExecuteChanged will be called when the setter of the property is called. In your scenario, that means only when the setter of the Arrivals property is called, this Attribute will call the IRelayCommand.NotifyCanExecuteChanged.
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).
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 exposed a collection and binded it to itemsource of autocompletebox which works but selecting or changing the text on the autocompletebox doesn't update the model like a textbox or a label!
viewmodel:
public ObservableCollection<String> SymptomsDb { get; private set; }
private String symptom;
public String Symptom
{
get { return symptom; }
set
{
symptom = value;
RaisePropertyChanged(() => this.Symptom);
}
}
public AnalysisViewModel()
{
List<String> s = new List<String>();
s.Add("test");
SymptomsDb = new ObservableCollection<String>(s);
}
view:
<controls:AutoCompleteBox
ItemsSource="{Binding SymptomsDb}"
SelectedItem="{Binding Symptom}"
Text="{Binding Symptom}"
IsTextCompletionEnabled="True"
FilterMode="Contains"/>
To get a change from the user interface back to the viewmodel, you will always need to bind the property TwoWay (except some properties like TextBox.TextProperty that are TwoWay by default):
<controls:AutoCompleteBox
ItemsSource="{Binding SymptomsDb}"
SelectedItem="{Binding Symptom, Mode=TwoWay}"
Text="{Binding Symptom}"
IsTextCompletionEnabled="True"
FilterMode="Contains"/>
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}" />