Xamarin.Forms - PropertyChanged set {} not called - c#

I've got a simple LoginView and LoginViewModel.
First, here is the related code of the View:
<StackLayout>
<Entry
Text="{Binding Email, Mode=TwoWay}"
/>
...
And here is the ViewModel:
public class LoginViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _email;
public string Email
{
get => _email;
set
{
_email = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Email)));
}
}
...
}
If I say inside the Constructor something like
Email = "abc";
the Entry for sure displays the value "abc". But if I change the Text inside the Entry, the set {} is not firing so the PropertyChanged() also does not.
Do I miss something here or do I have to use BindableProperties?
Thanks in advance!
Edit 1
For anyone needing the definition of the BindingContext for LoginView, here is the Code-Behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public sealed partial class LoginView
{
public LoginView()
{
InitializeComponent();
// Set the ViewModel
this.BindingContext = new LoginViewModel();
}
}
Edit 2
So I created a Testproject which simply binds the TextProperty of a Entry to a Property. This works! Then I edited my existing Code (removed Baseclasses, simplified everything, etc) to simply do the same basic thing... And it doesn't work. What could this be?

After I updated my Xamarin.Forms version, the bug now is gone!

Related

Sharing data between different ViewModels

I'm trying to develop an easy MVVM project that it has two windows:
The first window is a text editor, where I bind some properties such as FontSize or BackgroundColor:
<TextBlock FontSize="{Binding EditorFontSize}"></TextBlock>
its DataContext is MainWindowViewModel:
public class MainWindowViewModel : BindableBase
{
public int EditorFontSize
{
get { return _editorFontSize; }
set { SetProperty(ref _editorFontSize, value); }
}
.....
The second window is the option window, where I have an slider for changing the font size:
<Slider Maximum="30" Minimum="10" Value="{Binding EditorFontSize }" ></Slider>
its DataContext is OptionViewModel:
public class OptionViewModel: BindableBase
{
public int EditorFontSize
{
get { return _editorFontSize; }
set { SetProperty(ref _editorFontSize, value); }
}
.....
My problem is that I have to get the value of the slider in the option window and then I have to modify the FontSize property of my TextBlock with this value. But I don't know how to send the font size from OptionViewModel to MainViewModel.
I think that I should use:
A shared model
A model in MainWindowViewModel and a ref of this model in OptionViewModel
Other systems like notifications, messages ...
I hope that you can help me. It's my first MVVM project and English isn't my main language :S
Thanks
Another option is to store such "shared" variables in a SessionContext-class of some kind:
public interface ISessionContext: INotifyPropertyChanged
{
int EditorFontSize { get;set; }
}
Then, inject this into your viewmodels (you are using Dependency Injection, right?) and register to the PropertyChanged event:
public class MainWindowViewModel
{
public MainWindowViewModel(ISessionContext sessionContext)
{
sessionContext.PropertyChanged += OnSessionContextPropertyChanged;
}
private void OnSessionContextPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "EditorFontSize")
{
this.EditorFontSize = sessionContext.EditorFontSize;
}
}
}
There are many ways to communicate between view models and a lot of points what the point is the best. You can see how it is done:
using MVVMLight
in Prism
by Caliburn
In my view, the best approach is using EventAggregator pattern of Prism framework. The Prism simplifies MVVM pattern. However, if you have not used Prism, you can use Rachel Lim's tutorial - simplified version of EventAggregator pattern by Rachel Lim.. I highly recommend you Rachel Lim's approach.
If you use Rachel Lim's tutorial, then you should create a common class:
public static class EventSystem
{...Here Publish and Subscribe methods to event...}
And publish an event into your OptionViewModel:
eventAggregator.GetEvent<ChangeStockEvent>().Publish(
new TickerSymbolSelectedMessage{ StockSymbol = “STOCK0” });
then you subscribe in constructor of another your MainViewModel to an event:
eventAggregator.GetEvent<ChangeStockEvent>().Subscribe(ShowNews);
public void ShowNews(TickerSymbolSelectedMessage msg)
{
// Handle Event
}
The Rachel Lim's simplified approach is the best approach that I've ever seen. However, if you want to create a big application, then you should read this article by Magnus Montin and at CSharpcorner with an example.
Update: For versions of Prism later than 5 CompositePresentationEvent is depreciated and completely removed in version 6, so you will need to change it to PubSubEvent everything else can stay the same.
I have done a big MVVM application with WPF. I have a lot of windows and I had the same problem. My solution maybe isn't very elegant, but it works perfectly.
First solution: I have done one unique ViewModel, splitting it in various file using a partial class.
All these files start with:
namespace MyVMNameSpace
{
public partial class MainWindowViewModel : DevExpress.Mvvm.ViewModelBase
{
...
}
}
I'm using DevExpress, but, looking your code you have to try:
namespace MyVMNameSpace
{
public partial class MainWindowViewModel : BindableBase
{
...
}
}
Second solution: Anyway, I have also a couple of different ViewModel to manage some of these windows. In this case, if I have some variables to read from one ViewModel to another, I set these variables as static.
Example:
public static event EventHandler ListCOMChanged;
private static List<string> p_ListCOM;
public static List<string> ListCOM
{
get { return p_ListCOM; }
set
{
p_ListCOM = value;
if (ListCOMChanged != null)
ListCOMChanged(null, EventArgs.Empty);
}
}
Maybe the second solution is simplier and still ok for your need.
I hope this is clear. Ask me more details, if you want.
I'm not a MVVM pro myself, but what I've worked around with problems like this is,
having a main class that has all other view models as properties, and setting this class as data context of all the windows, I don't know if its good or bad but for your case it seems enough.
For a more sophisticated solution see this
For the simpler one,
You can do something like this,
public class MainViewModel : BindableBase
{
FirstViewModel firstViewModel;
public FirstViewModel FirstViewModel
{
get
{
return firstViewModel;
}
set
{
firstViewModel = value;
}
}
public SecondViewModel SecondViewModel
{
get
{
return secondViewModel;
}
set
{
secondViewModel = value;
}
}
SecondViewModel secondViewModel;
public MainViewModel()
{
firstViewModel = new FirstViewModel();
secondViewModel = new SecondViewModel();
}
}
now you have to make another constructor for your OptionWindow passing a view model.
public SecondWindow(BindableBase viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
this is to make sure that both windows work on the same instance of a view model.
Now, just wherever you're opening the second window use these two lines
var window = new SecondWindow((ViewModelBase)this.DataContext);
window.Show();
now you're passing the First Window's view model to the Second window, so that they work on the same instance of the MainViewModel.
Everything is done, just you've to address to binding as
<TextBlock FontSize="{Binding FirstViewModel.EditorFontSize}"></TextBlock>
<TextBlock FontSize="{Binding SecondViewModel.EditorFontSize}"></TextBlock>
and no need to say that the data context of First window is MainViewModel
In MVVM, models are the shared data store. I would persist the font size in the OptionsModel, which implements INotifyPropertyChanged. Any viewmodel interested in font size subscribes to PropertyChanged.
class OptionsModel : BindableBase
{
public int FontSize {get; set;} // Assuming that BindableBase makes this setter invokes NotifyPropertyChanged
}
In the ViewModels that need to be updated when FontSize changes:
internal void Initialize(OptionsModel model)
{
this.model = model;
model.PropertyChanged += ModelPropertyChanged;
// Initialize properties with data from the model
}
private void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(OptionsModel.FontSize))
{
// Update properties with data from the model
}
}
I'm new to WPF and I've come up with a solution to this and I'm curious of more knowledgeable people's thoughts about what's right and wrong with it.
I have an Exams tab and a Templates tab. In my simple proof of concept, I want each tab to "own" an Exam object, and to be able to access the other tab's Exam.
I define the ViewModel for each tab as static because if it's a normal instance property, I don't know how one tab would get the actual instance of the other tab. It feels wrong to me, though it's working.
namespace Gui.Tabs.ExamsTab {
public class GuiExam: INotifyPropertyChanged {
private string _name = "Default exam name";
public string Name {
get => _name;
set {
_name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName="") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class ExamsHome : Page {
public ExamsHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly ExamsTabViewModel ViewModel = new ExamsTabViewModel();
}
public class ExamsTabViewModel {
public GuiExam ExamsTabExam { get; set; } = new GuiExam() { Name = "Exam from Exams Tab" };
public GuiExam FromTemplatesTab { get => TemplatesHome.ViewModel.TemplatesTabExam; }
}
}
namespace Gui.Tabs.TemplatesTab {
public partial class TemplatesHome : Page {
public TemplatesHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly TemplatesTabViewModel ViewModel = new TemplatesTabViewModel();
}
public class TemplatesTabViewModel {
public GuiExam TemplatesTabExam { get; set; } = new GuiExam() { Name = "Exam from Templates Tab" };
public GuiExam FromExamTab { get => ExamsHome.ViewModel.ExamsTabExam; }
}
}
And then everything is accessible in the xaml:
TemplatesHome.xaml (excerpt)
<StackPanel Grid.Row="0">
<Label Content="From Exams Tab:"/>
<Label FontWeight="Bold" Content="{Binding FromExamTab.Name}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="Local Content:"/>
<TextBox Text="{Binding TemplatesTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>
ExamsHome.xaml (excerpt)
<StackPanel Grid.Row="0">
<Label Content="Local Content:"/>
<TextBox Text="{Binding ExamsTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="From Templates Tab:"/>
<Label FontWeight="Bold" Content="{Binding FromTemplatesTab.Name}"/>
</StackPanel>

Button command binding doesn't work in Xamarin.Forms

I'd like to bind a command to the command property of my button. This seemed pretty straightforward since I've done this many times before in WPF and the method here is very similar. Let me show some code snippets.
XAML
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.View.CustomPage"
Title="Something">
<ContentPage.Content>
<StackLayout>
<Button x:Name="numBtn" Text="Increase number" Command="{Binding IncreaseCommand}" />
<Label x:Name="numLabel" Text="{Binding numberText}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Code-behind
public partial class CustomPage : ContentPage
{
public CustomPage ()
{
InitializeComponent ();
BindingContext = ViewModelLocator.ViewModel(); //ViewModelLocator is singleton, gives
//you a ViewModel instance
}
}
ViewModel
public ICommand IncreaseCommand { get; private set; }
private int number;
public string numberText { get; private set;}
the constructor:
public ViewModel()
{
IncreaseCommand = new Command (() => IncreaseExecuted ());
number = 0;
numberText = number.ToString ();
OnPropertyChanged (numberText);
}
and then
private void IncreaseExecuted()
{
number++;
numberText = number.ToString ();
OnPropertyChanged (numberText);
}
When I run the app using the Xamarin Android Player (KitKat) I see the button, and the label reading 0. Then I press the button and nothing happens. I tried checking what happens with breakpoints but the app doesn't pause, not even when they're in the constructor of my ViewModel. I guess it's something to do with the emulator. Anyway, I think the binding is ok since I can see a "0" on the screen. What could be the problem? Let me show my ViewModelBase class just in case:
ViewModelBase
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Maybe my numberText property doesn't get updated when I call OnPropertyChanged? But I've used the exact same ViewModelBase class several times before and it always worked fine. One last thing, my CustomPage page is wrapped inside a NavigationPage which is a child of a TabbedPage:
MainPage.xaml.cs
this.Children.Add (new NavigationPage (new CustomPage ()) {Title="Something"} );
This shouldn't affect anything but there it is just in case. So what's wrong with my command binding? Thank you in advance!
This answer is not directly related to the problem of the original question, however this question is the one that ranks highest in search engines and the title of this question is ambiguous to the question that this answer answers.
I was having issues with a Command that wouldn't fire the associated command action. My problem was that I had defined the Command as a field instead of a property.
Works:
public Command MyCommand { get; set; }
Doesn't work:
public Command MyCommand;
Hope this helps someone else.
You are almost there. Take a close look at your call to the OnPropertyChanged method; you are passing the value of numberText and not the name. If you change your code to pass "numberText" I expect it shall work properly.
Edit: I should add that the the OnPropertyChanged call in the constructor has the same problem. The reason you see "0" at startup is that the view is simply using the existing binding to retrieve the value.
Edit 2: Now that Xamarin supports C# 6.0, you can use the new "nameof" expression that eliminates the need for a hard-coded string. Alternately, you can use MvvmCross, MvvmLight, or XLabs' MVVM classes.

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.

Binding variables to XAML fields

I am Using C# & XAML with Visual Studio 2012
MS changed much of Visual Studio in 2012 that I have not been able to find working solutions on the web. I am new to C#/XAML so I am not familiar with Data Binding, if that is indeed the proper way to proceed.
I need to display variables from the App.xaml.cs file on the MainPage.xaml page. These variables change state every 100-300 msec., so requiring a refresh of the page each time the data changes is probably not a good idea.
Here are code snippets from my project:
App.xaml.cs defines the variables and modifies them in a dispatcherTimer:
namespace OpenGOTO
{
public partial class App : Application
{
public static string DateStrZ = "";
public static string FubarTest { get; set; }
}
}
In MainPage.xaml (which is not always the current window) I have the TextBlock:
<TextBlock x:Name="UTC_Data" Text="2012-08-01 03:29:07Z" Padding="5" Style="{StaticResource TextBlockStyle1}" />
In MainPage.xaml.cs I have routines that are called by a dispatcherTimer that updates the fields:
public void SetFieldsTick()
{
UTC_Data.Text = App.DateStrZ;
}
If I change this to
public static void SetFieldsTick()
so that I can call it from the App.xaml.cs dispatcherTimer, I get the error message:
An object reference is required for the non-static field, method, or property 'OpenGOTO.MainPage.UTC_Data'
How do I either:
Bind the data to the field (and will it automatically update without needing to refresh the whole window?)
Create the correct references so that the dispatcherTimer in App.xaml.cs can call a routine in MainPage.xaml.cs that sets the fields in the XAML page.
To use a Binding that gets updates from the data you need a few things:
A property to bind to
Some implementation of change notification, usually using INotifyPropertyChanged or a DependencyProperty
An object instance on which the property is declared
You currently have none of these. Start by making an object that implements INotifyPropertyChanged with a property to store your data:
public class MyBindableObject : INotifyPropertyChanged
{
private string _dateStr;
public string DateStr
{
get { return _dateStr; }
set
{
if (_dateStr == value)
return;
_dateStr = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("DateStr"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can then expose a static instance of this from your App class and make updates to this instance whenever new data comes in:
private static MyBindableObject _bindingContainer = new MyBindableObject();
public static MyBindableObject BindingContainer
{
get { return _bindingContainer; }
}
public static void SetNewData()
{
// use this anywhere to update the value
App.BindingContainer.DateStr = "<Your New Value>";
}
Now you have everything you need for a Binding and you just need to expose it to your page. You can do this by setting the DataContext of your page, which is the default binding source:
public MainPage()
{
this.InitializeComponent();
DataContext = App.BindingContainer;
}
Now you can bind your TextBlock:
<TextBlock x:Name="UTC_Data"
Text="{Binding Path=DateStr}"
Padding="5" Style="{StaticResource TextBlockStyle1}"/>
Why can't you just call the UTC_Data from App.xaml.cs?
For example:
((MainPage) rootFrame.Content).UTC_Data.Text = DateStrZ;
Of course UTC_Data won't be accessible until you change it like this:
<TextBlock x:FieldModifier="public" x:Name="UTC_Data" Text="2012-08-01 03:29:07Z" Padding="5" Style="{StaticResource TextBlockStyle1}"/>

Binding to a ViewModel's Child Class Properties

I'm struggling to be able to bind a StatusBarItem content element in my view to a subclasses property in my ViewModel, I'm using the MVVM-Light framework/
ViewModel:
public class PageMainViewModel : ViewModelBase
{
LoggedOnUserInfo UserInfo;
public LoggedOnUser UserInfo
{
set
{
_UserInfo = value;
RaisePropertyChanged("UserInfo");
}
}
}
For full clarity the LoggedOnUser Class is defined as follows
public class LoggedOnUser : INotifyPropertyChanged
{
private string _Initials;
public event PropertyChangedEventHandler PropertyChanged;
public LoggedOnUser()
{
}
[DataMember]
public string Initials
{
get { return _Initials; }
set
{
_Initials = value;
OnPropertyChanged("Initials");
}
}
protected void OnPropertyChanged(string propValue)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propValue));
}
}
}
My Views DataContext is being set and is working as I am able to see other bindings working, but my attempts to bind to UserInfo.Initials property in my XAML are producing an empty result.
XAML:
<StatusBarItem Grid.Column="0" Content="{Binding UserInfo.Initials}" Margin="5,0,0,0" VerticalAlignment="Center" Focusable="False" />
The UserInfo property is set after the viewModel is created due to several factors but I thought with my propertychanged events this would be ok.
Any Advice on this would be greatly appreciated.
You do not appear to have a getter on UserInfo, the binding will be out of luck.
(Also check for binding errors when having trouble with bindings, they probably will tell you about all their problems)
add the getter to your userinfo
public class PageMainViewModel : ViewModelBase
{
LoggedOnUserInfo UserInfo;
public LoggedOnUser UserInfo
{
get {return _UserInfo;}
set
{
_UserInfo = value;
RaisePropertyChanged("UserInfo");
}
}
}
and like H.B. said - check your output window for binding errors
I am not quite sure why you have the initials_ attribute bound inside the UserInfo_ attribute.
You can't access the initials_ attribute without a getter of the UserInfo_ attribute.
I would suggest to bind to the latter separately.

Categories

Resources