Xamarin BindableProperty in custom control - c#

I am using a bindable property in a custom control in order to set a property from the xaml code. However, it seems like my property always will get the default value that I've specified for the bindable property.
My xaml code:
<controls:MyView ID="4" />
My code behind:
public partial class MyView : ContentView
{
public static readonly BindableProperty IDProperty = BindableProperty.Create(
nameof(ID),
typeof(int),
typeof(MyView),
15);
public int ID
{
get
{
return (int)GetValue(IDProperty);
}
set
{
SetValue(IDProperty, value);
}
}
private MyViewViewModel viewModel;
public MyView()
{
InitializeComponent();
viewModel = new MyViewViewModel() {};
BindingContext = viewModel;
}
}
I expect that my property should get value 4 in this example, but it always get the default value 15. Should the property be set in the constructor or later?
What am I doing wrong?

Why do you embed a ViewModel inside your custom control? It is weird and even wrong. The idea behind a custom control is that you could reuse and bind it to the parent's ViewModel. Think of a simple Button control, it is reusable by simple placing it on the screen and setting the BindableProperties like Text, Command and etc. It is working because it's BindingContext by default is the same as it's parent.
In your case you sort of isolate your control from any modifications, since you set the BindingContext to a private custom ViewModel class. You have to rethink your solution.
It should be as simple as:
public partial class MyView : ContentView
{
public static readonly BindableProperty IDProperty = BindableProperty.Create(
nameof(ID),
typeof(int),
typeof(MyView),
15);
public int ID
{
get => (int)GetValue(IDProperty);
set => SetValue(IDProperty, value);
}
public MyView()
{
InitializeComponent();
}
}

I see it's too late, but i have been suffering for a while now, that - for a reason that i don't know - a ContentView(Custom view element) won't bind when you set it's BindingContext property in any way other than this:
<ContentView x:Class="mynamespace.CustomViews.MyView"
....
x:Name="this">
then on the main container element (in my case a frame) set the BindingContext
<Frame BindingContext="{x:Reference this}"
....>
Setting the BindingContext in the constructor - in MyView.xaml.cs - does not work, while this way - and other ways - work in binding a View to another class (a view model), it does not - i repeat - work in binding ContentView to its code_behind.cs file.

In you xaml , do
<controls:MyView ID="{Binding Id}" />
And then in ViewModel, Create a porperty called Id
public int Id {get; set;} = 4;

You don't need Bindable property if your are not binding , Just Create a Normal Property of type int With ID as property name.And then you can assign the ID from XAML.(Intellisense will also show the ID property)
public int ID
{
get;set;
}

Related

Defining command in base View model

We would like define some button be visible in every page for a set of users. We defined button as control template for view and called out it in the pages that require them to be displayed.
<Button ImageSource="home_footer.png" BackgroundColor="Transparent" HorizontalOptions="FillAndExpand"
Command="{TemplateBinding BindingContext.HomeCommand}"/>
Here command is selected according to the binding context, i.e corresponding view model. Command need to written multiple times. I thought of including the command in base view model so that it needs to written only once. Is there any option for implementing this option.
Thanks in advance.
In your BaseViewModel add a command
public ICommand HomeCommand { get; set; }
In your base ContentPage add a bindable property that supplies you this command:
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
public static readonly BindableProperty CommandProperty = BindableProperty.Create(
nameof(Command),
typeof(ICommand),
typeof(ClassName),
null
);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
nameof(CommandParameter),
typeof(object),
typeof(ClassName),
null
);
Supply a command to this property from the ContentPage
<base:CustomContentPage ....
Command="{Binding HomeCommand}">
Set it to your Button something like:
You can give it a better name to explain the behaviour,
Goodluck!
Feel free to get back if you have queries!

Can't programmatically data bind 3 objects in WPF

I'm trying to data bind a Slider control to a custom view model, and then bind the same property from the view model to a custom type that inherits from DependencyObject. The binding mode between the view model and the Slider control is two way, but the mode between the view model and the custom DependencyObject type should only be one way (the type should not be able to change the view model value).
Here's the relevant bit from my view model:
public class ScanViewModel : DependencyObject
{
public static readonly DependencyProperty CurrentScanIndexProperty = DependencyProperty.Register("CurrentScanIndex", typeof(Int32), typeof(ScanViewModel), new PropertyMetadata(0));
public Int32 CurrentScanIndex
{
get { return (Int32)GetValue(CurrentScanIndexProperty); }
set { SetValue(CurrentScanIndexProperty, value); }
In my XAML I bind the slider control as follows:
<Slider x:Name="scanIndexSlider" Minimum=0 Maximum = 100 Value="{Binding CurrentScanIndex, Mode=TwoWay, Delay=5}"
I have a 3rd object that participates as well:
public class CustomIndicator : DependencyObject
{
public static readonly DependencyProperty ScanIndexProperty = DependencyProperty.Register("ScanIndex", typeof(Int32), typeof(CustomIndicator), new PropertyMetadata(0));
public Int32 ScanIndex
{
get { return (Int32)GetValue(ScanIndexProperty); }
set { SetValue(ScanIndexProperty, value); }
}
public CustomIndicator(ScanViewModel ViewModel)
{
// Data bind to the view model programmatically:
Binding binding = new Binding("CurrentScanIndex");
binding.source = ViewModel;
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(this, CustomIndicator.ScanIndexProperty, binding);
}
I assign an instance of "ScanViewModel" as the DataContext to the view containing the slider and the binding works i.e. I manipulate the slider and the dependency property on the view model changes to reflect the new slider value. However, the view models' new value is not then passed on to the "CustomIndicator.ScanIndex" dependency property that it was bound to during the CustomIndicator constructor method. If I run through step by step I can see the binding in the constructor seems to work initially...after the programmatic bindg in customIndicator contructor is executed the objects' "ScanIndex" reflects the same value as the view model to which it was just bound, so the binding works initially. However, "ScanIndex" on "CustomIndicator" never changes after that initial change. It's as if the binding works once (in the constructor) and then never again after that. As I mentioned, the binding between the Slider control and the view model works fine.
I should add that after instantiation the "CustomIndicator" object is then added to the Children collection of a custom UserControl that has its own DataContext (a different type). Could this be the problem?

Expose child properties of user control directly as property of user control

Without breaking MVVM, is there a way to expose some properties of a child control in a user control so that the window or other user control that utilizes it can access these properties directly?
For instance I have a user control that has a listview set up with gridviewcolumns, headers, and is bound to a view model. But the list view in the user control has selected item properties and such that I'd like to expose to the host without having to do something like usercontrol.customListView.property. Or is that how I should do it? I'd like to go just usercontrol.property, omitting customListView. Perhap I should just create properties in the user controls code behind that return the list view controls properties that I want attached directly to the user control?
I feel like that latter option doesn't really break MVVM since they are exposed for the host to interact with, not really related to the view itself. Any suggested would be appreciated.
EDIT: In fact, I'd really like to have a SelectedItem property directly on the user control that is not ListViewItem or object, but actually of the datatype contained that doe like:
public MyDataType SelectedItem {
get {
return customListView.SelectedItem as MyDataType;
}
}
Would that be permissible in MVVM? Because I don't see how I could have that in the ViewModel, seems like it would have to be in the partial class code behind.
This is pretty common task when you want to put something repeated into UserControl. The simplest approach to do so is when you are not creating specialized ViewModel for that UserControl, but sort of making custom control (build with the use of UserControl for simplicity). End result may looks like this
<UserControl x:Class="SomeNamespace.SomeUserControl" ...>
...
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}" ...>
</UserControl>
.
public partial class SomeUserControl : UserControl
{
// simple dependency property to bind to
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(SomeUserControl), new PropertyMetadata());
// has some complicated logic
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(SomeUserControl),
new PropertyMetadata((d, a) => ((SomeUserControl)d).ValueChanged()));
private void ValueChanged()
{
... // do something complicated here
// e.g. create complicated dynamic animation
}
...
}
Usage will looks like this in containing window
<l:SomeUserControl Text="Text" Value="{Binding SomeValue}" ... />
As you can see SomeValue is bound to Value and there is no MVVM violations.
Of course, you can create a proper ViewModel if view logic is complicated or required too much bindings and it's rather easier to allow ViewModels to communicate directly (via properties/methods).

Store grid rowcount in ViewModel in MVVM

I'm implementing MVVM for a WPF application.
The ViewModels are created as follows:
ViewModel: base class from which all ViewModels override
MainTemplateViewModel: the 'Masterpage' ViewModel which contains a ViewModel property Current that contains the ViewModel to show
CustomerOverviewViewModel: an example of a view that can be placed in the MainTemplateViewModel.Current
The CustomerGridViewModel contains a Telerik GridView. I would like to show the number of items in the title of the MainTemplateViewModel. The GridView.Items.Count property implements the INotifyPropertyChanged so I would like to bind this property to ViewModel.RowCount (because the CustomerGridViewModel doesn't know it is part of the MainTemplateViewModel it cannot be bound directly to the TextBlock). I can in turn use ViewModel.NumberOfRecords to show the amount in the title.
How can I bind the Count property to a property in my ViewModel?
Edit
I'll describe the issue in more detail:
The list of objects shown in the grid is a binding from the ViewModel:
<telerik:RadGridView x:Name="CustomerGrid" ItemsSource="{Binding CustomerViewModels}">
</telerik:RadGridView>
When filtering the Grid in memory, the Telerik Grid automatically changes the GridView.Items.Count property (this does not mean the original list count is changed!). So if I can bind this property to a property in the ViewModel class, this would solve the problem.
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
private int numberOfRecords;
public int NumberOfRecords
{
get { return numberOfRecords; }
set { numberOfRecords = value; OnPropertyChanged(); }
}
}
MainTemplateViewModel.cs
public class MainTemplateViewModel : ViewModel
{
private ViewModel current = new MainOverviewViewModel();
public ViewModel Current
{
get { return current; }
set
{
if (current != value)
{
current = value; OnPropertyChanged();
}
}
}
}
CustomerOverview.xaml.cs
public partial class CustomerOverview : UserControl
{
public CustomerOverview()
{
InitializeComponent();
this.CustomerGrid.Items.CollectionChanged += ItemsCollectionChanged;
this.CustomerGrid.Loaded += CustomerGrid_Loaded;
}
void CustomerGrid_Loaded(object sender, RoutedEventArgs e)
{
/* METHOD 1 PROBLEM: the field to bind to in the MainTemplate is out of scope and accessing a view is not MVVM */
var binding = new Binding();
binding.Path = new PropertyPath("Items.Count");
binding.Source = CustomerGrid;
((MainWindow)this.ParentOfType<MainWindow>()).NumberOfRecords.SetBinding(TextBlock.TextProperty, binding);
}
void ItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
/* METHOD 2 PROBLEM: codebehind code should be in viewmodel */
((CustomerOverviewViewModel)this.DataContext).NumberOfRecords = CustomerGrid.Items.Count;
}
}
Instead of loading the data in your UserControl, just declare a DependencyProperty of the relevant type in it. You can then load the data in the main view model and simply data bind to it from the UserControl. You could do something like this simple example:
In CustomerOverview.xaml.cs:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items",
typeof(ObservableCollection<YourDataType>), typeof(CustomerOverview),
new PropertyMetadata(null));
...
In CustomerOverview.xaml:
<ListView ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType={
x:Type YourPrefix:CustomerOverview}}}" ... />
...
In MainWindow.xaml (or whichever relevant view):
<YourPrefix:CustomerOverview
Items="{Binding SomeCollectionPropertyInMainTemplateViewModel}" ... />
...
In MainTemplateViewModel.cs (or whichever relevant view model):
public ObservableCollection<YourDataType> SomeCollectionPropertyInMainTemplateViewModel
{
get { return someCollectionPropertyInMainTemplateViewModel; }
set
{
someCollectionPropertyInMainTemplateViewModel = value;
NotifyPropertyChanged("SomeCollectionPropertyInMainTemplateViewModel");
NotifyPropertyChanged("NumberOfRecords");
}
}
public int NumberOfRecords
{
get { return someCollectionPropertyInMainTemplateViewModel.Count; }
}
Telerik Grid has two properties
Visible Count
TelerikGrid.Items.Count
Total Count
TelerikGrid.Items.TotalItemCount
In case this helps!
If i get you right, you want your master to show details of the child.
Your master should be able to know your child by your Current property.
If you are using MVVM correctly, the data bound to your grid comes from the child-ViewModel.
In that case, you have already have the itemscount in your child-ViewModel.
After this you can say in your Master something like
<Label Content="{Binding Current.NumberOfRows}"></Label>
According to this page you could wrap your source in a QueryableCollectionView

Binding an ObservableCollection to ListBox (Easy case?)

I am really struggling to understand binding. I know there are loads of other threads with much the same title as this one, but they're all trying to do something more complex than I am, and all the answers assume a whole pile of stuff that I just don't get :(
I'm trying to display a dynamically updated message log. I've defined a Message class:
public class Message
{
public DateTime Timestamp { get; private set; }
public string Value { get; private set; }
public int Severity { get; private set; }
public Message(string value, int severity)
{
Timestamp = DateTime.Now;
Value = value;
Severity = severity;
}
}
I've defined a MessageLog class as simply:
public class MessageLog: ObservableCollection<Message>
{
public MessageLog(): base()
{ }
}
In my MainWindow constructor I have a Log property:
public MessageLog Log { get; private set; }
In the MainWindow constructor I initialise Log:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Log = new Model.MessageLog();
// and so on
}
In the XAML for the main window I have:
<ListBox Name="MessagePanel" Height="100" ItemsSource="{Binding MessageLog}" IsEnabled="False"/>
Now if I add Message instances to the MessageLog I expected to see them appear in the ListBox. They don't. What have I missed?
Thanks in advance (and if you can point me somewhere that explains bindings clearly -- especially the view that XAML has of the code and where it can look for things -- then many more thanks on top. At the moment I'm using Matthew McDonald's "Pro WPF 4.5 in C#" and I'm just not getting it.)
Change your constructor:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Log = new Model.MessageLog();
}
to this:
public MainWindow()
{
InitializeComponent();
Log = new Model.MessageLog(); // <- This line before setting the DataContext
DataContext = this;
}
Explanation:
Setting properties after having set the DataContext requires your class to implement INotifyPropertyChanged and raise change notifications after properties are set.
Since you're setting the DataContext before setting the property, the value of this.Log is null at the time of DataBinding, and WPF is never notified that it ever changed.
That being said, you don't usually put Data inside UI Elements (such as Window). The accepted and recommended approach to WPF is MVVM, where you usually create a ViewModel and set that as the Window's DataContext:
public class MyViewModel
{
public MessageLog Log {get;set;}
public MyViewModel()
{
Log = new MessageLog();
}
}
Window Constructor:
public MainWindow
{
DataContext = new MyViewModel();
}
Your collection property name is Log which is what you should be binding to in ItemsSource property; and if you have not done a typo in your question then you are binding wrongly to MessageLog, and change Binding as below:
<ListBox Name="MessagePanel" Height="100" ItemsSource="{Binding Log}" IsEnabled="False"/>
For more information and learning on Data Binding in WPF (4.5), see MSDN Data Binding Overview
The datacontext of the view must be the viewmodel.

Categories

Resources