Data bindings do not see viewmodel properties - c#

Problem:
I am trying to create what seems to be a simple MVVM view setup. However, no matter what I modify, I can't seem to make the PropertyChanged hookup connect to the .xaml and vice-versa.
Here's the View:
VpicInformationPage.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"
xmlns:viewModels="clr-namespace:ScanditBarcodeScanner.ViewModels"
x:Class="ScanditBarcodeScanner.Pages.VehicleOperationPages.VpicInformationPage">
<!--<ContentPage.BindingContext>
<viewModels:VpicInformationPageViewModel />
</ContentPage.BindingContext>-->
<StackLayout VerticalOptions="CenterAndExpand" Padding="5">
<StackLayout.BindingContext>
<viewModels:VpicInformationPageViewModel />
</StackLayout.BindingContext>
<Entry x:Name="VinEntry" Placeholder="VIN (Manual Entry)" />
<Label Text="{Binding VinType.Make}" />
<Label Text="{Binding VinType.Model}" />
<Label Text="{Binding VinType.ModelYear}" />
<Label Text="{Binding VinType.BodyClass}" />
<Label Text="{Binding VinType.ErrorCode}" />
<Button Text="Scan/Check VIN" Clicked="ScanOrCheckVin_Clicked"/>
</StackLayout>
</ContentPage>
The Model:
VpicInformationPage.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Scandit.BarcodePicker.Unified;
using Scandit.BarcodePicker.Unified.Abstractions;
using ScanditBarcodeScanner.ViewModels;
using ScanditBarcodeScanner.ViewModels.Base;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ScanditBarcodeScanner.Pages.VehicleOperationPages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class VpicInformationPage : ContentPage, INotifyPropertyChanged
{
IBarcodePicker _picker;
VpicInformationPageViewModel ViewModel;
public VpicInformationPage()
{
InitializeComponent ();
ViewModel = BindingContext as VpicInformationPageViewModel;
ViewModel.VinType = VehicleApi.EmptyVinType;
_picker = ScanditService.BarcodePicker;
SetVinSettings();
_picker.DidScan += OnDidScan;
VinEntry.Text = "";
}
//...
}
}
The ViewModel:
VpicInformationPageViewModel.cs
using ScanditBarcodeScanner.ViewModels.Base;
using System.ComponentModel;
namespace ScanditBarcodeScanner.ViewModels
{
public class VpicInformationPageViewModel : ViewModelBase
{
#region VinType
private VehicleApi.VinType _vinType;
public VehicleApi.VinType VinType
{
get { return _vinType; }
set
{
SetValue(ref _vinType, value, "VinType");
Make = _vinType.Make;
Model = _vinType.Model;
ModelYear = _vinType.ModelYear;
BodyClass = _vinType.BodyClass;
ErrorCode = _vinType.ErrorCode;
}
}
#endregion VinType
#region VinType.Make
private string _make;
public string Make
{
get { return _vinType.Make; }
private set
{
SetValue(ref _make, value);
}
}
#endregion VinType.Make
#region VinType.Model
private string _model;
public string Model
{
get { return _vinType.Model; }
private set
{
SetValue(ref _model, value);
}
}
#endregion VinType.Model
#region VinType.ModelYear
private string _modelYear;
public string ModelYear
{
get { return _vinType.ModelYear; }
private set
{
SetValue(ref _modelYear, value);
}
}
#endregion VinType.ModelYear
#region VinType.BodyClass
private string _bodyClass;
public string BodyClass
{
get { return _vinType.BodyClass; }
private set
{
SetValue(ref _bodyClass, value);
}
}
#endregion VinType.BodyClass
#region VinType.ErrorCode
private string _errorCode;
public string ErrorCode
{
get { return _vinType.ErrorCode; }
private set
{
SetValue(ref _errorCode, value);
}
}
#endregion VinType.ErrorCode
public VpicInformationPageViewModel()
{
_vinType = new VehicleApi.VinType();
}
}
}
The ViewModelBase:
ViewModelBase.cs
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ScanditBarcodeScanner.ViewModels.Base
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
protected bool SetValue<T>(ref T BackingField, T Value, [CallerMemberName] string PropertyName = null)
{
if(EqualityComparer<T>.Default.Equals(BackingField, Value))
{
return false;
}
BackingField = Value;
OnPropertyChanged(PropertyName);
return true;
}
}
I realized that I didn't include the VehicleApi class. Here is the important part of that class:
VehicleApi.cs
namespace ScanditBarcodeScanner
{
public static class VehicleApi
{
public static VinType EmptyVinType { get; } = new VinType
{
Make = "Make",
Model = "Model",
ModelYear = "Model Year",
BodyClass = "Body Class",
ErrorCode = "Status/Error Code"
};
public class VinType
{
public string Make { get; set; }
public string Model { get; set; }
public string ModelYear { get; set; }
public string BodyClass { get; set; }
public string ErrorCode { get; set; }
}
}
It is my understanding that I have implemented these files correctly and linked them together properly. However, every time I run the app, I get:
[0:] Binding: 'VinType' property not found on 'ScanditBarcodeScanner.ViewModels.VpicInformationPageViewModel', target property: 'Xamarin.Forms.Label.Text'
I have bound the View to the ViewModel, and implemented INotifyPropertyChanged.
I have tried many solutions, such as changing where I set the binding context (either in the ContentPage or in the StackLayout, or even both), trying different methods of notifying the view that properties have changed, and binding the labels to the underlying members of VinType, and allowing VinType to modify and raise PropertyChanged for them. I even tried PropertyChanged.Fody, but it seems the issue is not with the notifying code, but with the way I have bound the View and ViewModel together, or perhaps with how the properties are defined.
Question:
What part of the binding hookup is missing/incorrect? The documentation states that I should just be able to access VinType and its members with the code I have provided.
Link to sample
The issue with the project above, is that, while it doesn't give the same error I'm talking about, it still doesn't change the fields when it's supposed to.

So, the fix to this issue was actually something rather simple.
I just needed to change the label definitions from
<Label Text="{Binding PropertyName.Member}" /> to <Label Text="{Binding PropertyName.Member, StringFormat='{}{0}'}">
The reason there needs to be the {} in the format string is because of an error that comes up when you use single quotes '' instead of double quotes "" when defining StringFormat.
EDIT: While the StringFormat issue is important, the larger issue is that the properties in the objects I was using did not have get or set defined.
As long as a Type is something along the lines of this:
public class TypeName
{
public Type Property1 { get; set; }
public Type Property2 { get; set; }
}
Then the binding engine will be able to see and edit the member properties.

Related

Custom control Bindable property don't work with ViewModel

I encounter an issue while I'm creating a Custom control in my Xamarin forms app.
I create a bindable property for the label label called "NameLabel" in this xaml file :
<Grid
x:Class="Kwikwink.Controls.SelectedGatewayFrame"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit">
<StackLayout>
<Frame >
<StackLayout Orientation="Horizontal">
<StackLayout>
...
<Label
x:Name="NameLabel"
FontFamily="InterMed"
TextColor="{StaticResource DetailTextColor}"
VerticalOptions="Center" />
</StackLayout>
...
</StackLayout>
</Frame>
</StackLayout>
</Grid>
with this code in my xaml.cs file
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SelectedGatewayFrame : Grid
{
public static readonly BindableProperty GatewayNameProperty = BindableProperty.Create(nameof(Name),
typeof(string),
typeof(SelectedGatewayFrame),
defaultValue: string.Empty,
propertyChanged: GatewayNamePropertyChanged);
private static void GatewayNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (SelectedGatewayFrame)bindable;
control.NameLabel.Text = newValue?.ToString();
}
public string Name
{
get => GetValue(GatewayNameProperty)?.ToString();
set => SetValue(GatewayNameProperty, value);
}
public SelectedGatewayFrame()
{
InitializeComponent();
}
}
And finaly a use it like this in other views:
<controls:SelectedGatewayFrame Name="Test string" />
With the string set here it work perfectly !!
My problem come when I try to bound this property to somthing in a viewmodel (public string tmp { get; set; } = "meph"; for exemple) :
like so :
<controls:SelectedGatewayFrame Name="{Binding tmp}" />
or so :
<controls:SelectedGatewayFrame Name="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:NewAccessTypeViewModel}}, Path=tmp}" />
Then I got this error that show :
No property, BindableProperty, or event found for "Name", or mismatching type between value and property.
I hope this is clear enough, thanks in advance for any help
As a summary, I posted an answer.
For the first problem:
No property, BindableProperty, or event found for "Name", or
mismatching type between value and property.
From document Create a property,we know that
Important
The naming convention for bindable properties is that the bindable
property identifier must match the property name specified in the
Create method, with "Property" appended to it.
So, you need to change GatewayNameProperty to NameProperty or change public string Name to public string GatewayName.
For example:
public static readonly BindableProperty GatewayNameProperty = BindableProperty.Create(nameof(GatewayName),
typeof(string),
typeof(SelectedGatewayFrame),
defaultValue: string.Empty,
propertyChanged: GatewayNamePropertyChanged);
public string GatewayName
{
get => GetValue(GatewayNameProperty)?.ToString();
set => SetValue(GatewayNameProperty, value);
}
For the second problem:
when have a default value in the view model it perfectly work but if i
try to set it later it stay null.
You need to implement interface INotifyPropertyChanged for your view model.
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For exmaple:
public class NewAccessTypeViewModel: INotifyPropertyChanged
{
Gateway currentGateway;
public Gateway CurrentGateway
{
get
{
return currentGateway;
}
set
{
if (currentGateway != value)
{
currentGateway = value;
OnPropertyChange(nameof(CurrentGateway));
}
}
}
public NewAccessTypeViewModel() { CurrentGateway = null; }
public NewAccessTypeViewModel(Gateway _gateway)
{ CurrentGateway = _gateway; Console.WriteLine($"Current gateway name => {CurrentGateway.Name}");
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And you also need to implement interface INotifyPropertyChanged for your Gateway .
public class Gateway: INotifyPropertyChanged
{
// public string Name { get; set; }
private string _name ;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChange(nameof(Name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}

Bind text field to a value that is fetched from a REST Api using Xamarin

I've started a Xamarin project using the template that is available in Visual Studio.
I wrote some services that fetches data from REST api. I have printed these values to the console and I am able to see that the services work like they should.
However I have troubles displaying the name of a user that I fetched from the Api.
I'm trying to do this in a ViewModel class.
using MyApp.Models;
using System;
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;
using MyApp.Services;
using System.Threading.Tasks;
namespace MyApp.ViewModels
{
public class AboutViewModel : BaseViewModel
{
static readonly UserService CurrentUser = new UserService();
public AboutViewModel()
{
//GetData().Wait();
Title = "My App";
OpenWebCommand = new Command(async () => await Browser.OpenAsync("https://aka.ms/xamain-quickstart"));
IsConnected = false;
GetData();
}
protected async void GetData()
{
user = await CurrentUser.GetUser();
Name = user.fullName;
}
public ICommand OpenWebCommand { get; }
public bool IsConnected { get; set; }
public string Name { get; set; }
public User user { get; set; }
}
}
This is the line from the .xaml file where it would be displayed:
<Label Text="{Binding Name}" FontSize="Title"/>
I know this is bad practice but I tried to hack it into working. It still doesn't work, and it displays nothing. It's the only thing I've managed so far that doesn't crash or make the app freeze. How bind to the variable "Name", when "Name" will eventually be set from an async void or task?
Your BaseViewModel should probably implement the INotifyPropertyChanged interface or inherit from a class that already implements it, if you are using a MVVM framwork of sorts.
This could look something like:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T backingField, T newValue, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, newValue))
{
return false;
}
backingField = newValue;
RaisePropertyChanged(propertyName);
return true;
}
}
Then for your properties in your ViewModel you could use SetProperty to ensure PropertyChanged is fired when their values change. This is needed for the UI they are bound to, to figure out that something changed.
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
If you don't want to use SetProperty simply use RaisePropertyChanged:
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
RaisePropertyChanged();
}
}

Understanding MVVM and ObjectDetailViewModel in Udemy Course

I've been doing a course for Xamarin and I am at the MVVM portion where all the xaml.cs code is moved to ViewModels.
This is the course (lecture #104, 105, 106): [url]https://www.udemy.com/course/complete-xamarin-developer-course-ios-and-android[/url]
So far I've been able to understand the concept of MVVM, however, the course seems to do a few things incorrectly (as I believe) and some of the code in previous lectures isn't in the new lectures (we'll leave that annoyance alone for now), moving forward, I am not sure how to code it correctly. I also understand that I am presenting a simple use case at the moment so let's think that Post is a huge object that shouldn't be re-created over and over.
For example, I have a PostDetailPage.xaml(.cs), in the xaml, I currently have the following (note that most of the Mode=TwoWay might not be used properly):
<ContentPage.Content>
<StackLayout Margin="10">
<Entry x:Name="experienceEntry"
Text="{Binding Experience, Mode=TwoWay}"
Margin="-5"></Entry>
<label x:name="venuename"
text="{binding post.venuename, mode=onetime}"
fontattributes="bold"/>
<label x:name="categoryname"
text="{binding post.categoryname, mode=onetime}"/>
<label x:name="address"
text="{binding post.address, mode=onetime}"/>
<label x:name="coordinatelabel"
text="{binding coordinates, mode=onetime}"/>
<label x:name="distance"
text="{binding post.distance, mode=onetime, stringformat='{0:0}'}"/>
<Button Text="Update"
x:Name="updateButton"
Command="{Binding UpdatePostCommand}"
CommandParameter="{Binding Post}" />
</StackLayout>
</ContentPage.Content>
Code behind is the following:
public partial class PostDetailPage : ContentPage
{
PostDetailViewModel viewModel;
public PostDetailPage(Post selectedPost)
{
InitializeComponent();
viewModel = new PostDetailViewModel(selectedPost);
BindingContext = viewModel;
}
}
Then I have an incomplete PostDetailViewModel:
public class PostDetailViewModel : INotifyPropertyChanged
{
public UpdatePostCommand UpdatePostCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public PostDetailViewModel(Post post)
{
UpdatePostCommand = new UpdatePostCommand(this);
SelectedPost = post;
}
//Can this become another ViewModel or ?
private Post selectedPost;
public Post SelectedPost
{
get { return selectedPost; }
set
{
selectedPost = value;
OnPropertyChanged("Post");
}
}
private string experience;
public string Experience
{
get { return experience; }
set
{
experience = value;
UpdatePostObject();
OnPropertyChanged("Experience");
}
}
private void UpdatePostObject() {
Post = new Post() {
Experience = experience,
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
}
}
private void UpdatePostObject()
{
//This is used to Trigger the change on Post
//I don't think this is the best way (hence my question(s))
SelectedPost = new Post()
{
Experience = experience
};
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public async void Update()
{
await App.Current.MainPage.Navigation.PushAsync(new HistoryPage());
}
}
From the course, the instructor recommends that when Experience is changed (or any other property related to Post), that we re-create the Post object again and again to trigger CanExecute and associated Execute. This seems incorrect to have to re-create the Post object again and again after each change, so would updating only what is needed be best?
So, what I am asking (or learning about) is...
Should I implement a generic PostViewModel that is updaded when Experience has changed and somehow attach the ICommand to it, if so, how would something like that look like? Of course, if this is not correct, please point me in the right direction if possible.
Is there some type of OnObjectChanged like method that could be used? I know of the ObservableCollection when dealing with collections.
Please let me know if there is anything else I can provide.
Thank you
Derek
First of all I cannot open your url, this course is missing.
Should I implement a generic PostViewModel that is updaded when Experience has changed and somehow attach the ICommand to it, if so, how would something like that look like? Of course, if this is not correct, please point me in the right direction if possible.
For the UpdatePostObject method, you do not need to new a Post object, If you want to set the new value for the selectedPost object, just set it like following code directly,
private void UpdatePostObject()
{
selectedPost.Experience = experience;
selectedPost.address= "new Adree";
selectedPost.categoryname = "new CateGoryName";
selectedPost.distance = "41";
selectedPost.venuename = "new venu";
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
// };
}
If you want to achieve the click command, you just declear the public ICommand UpdatePostCommand { get; set; } in PostDetailViewModel. Then achieve it in your PostDetailViewModel's constructor. Here is my all code about PostDetailViewModel
public class PostDetailViewModel
{
//: INotifyPropertyChanged
public ICommand UpdatePostCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public PostDetailViewModel(Post post)
{
UpdatePostCommand = new Command<Post>((key) => {
key.categoryname = "Command categoryname";
key.distance = "31";
key.address = "Command address";
key.venuename = "Command venuename";
key.Experience = "Command Experience";
});
SelectedPost = post;
}
//Can this become another ViewModel or ?
private Post selectedPost;
public Post SelectedPost
{
get { return selectedPost; }
set
{
selectedPost = value;
// OnPropertyChanged("SelectedPost");
}
}
private string experience;
public string Experience
{
get { return experience; }
set
{
experience = value;
UpdatePostObject();
// OnPropertyChanged("Experience");
}
}
private void UpdatePostObject()
{
selectedPost.Experience = experience;
selectedPost.address= "new Adree";
selectedPost.categoryname = "new CateGoryName";
selectedPost.distance = "41";
selectedPost.venuename = "new venu";
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
// };
}
//private void UpdatePostObject()
//{
// //This is used to Trigger the change on Post
// //I don't think this is the best way (hence my question(s))
// SelectedPost = new Post()
// {
// Experience = experience
// };
//}
//public event PropertyChangedEventHandler PropertyChanged;
//protected virtual void OnPropertyChanged(string propertyName)
//{
// PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
//}
public async void Update()
{
await App.Current.MainPage.Navigation.PushAsync(new HistoryPage());
}
}
}
If you want to achieve the attribute change at the running time, you should achieve the INotifyPropertyChanged interface for all of your attribute in your POST model.
public class Post:INotifyPropertyChanged
{
// public string _venuename { get; set; }
// public string categoryname { get; set; }
string _categoryname;
public string categoryname
{
set
{
if (_categoryname != value)
{
_categoryname = value;
OnPropertyChanged("categoryname");
}
}
get
{
return _categoryname;
}
}
//public string address { get; set; }
string _address;
public string address
{
set
{
if (_address != value)
{
_address = value;
OnPropertyChanged("address");
}
}
get
{
return _address;
}
}
// public string Experience { get; set; }
string _experience;
public string Experience
{
set
{
if (_experience != value)
{
_experience = value;
OnPropertyChanged("Experience");
}
}
get
{
return _experience;
}
}
// public string distance { get; set; }
string _distance;
public string distance
{
set
{
if (_distance != value)
{
_distance = value;
OnPropertyChanged("distance");
}
}
get
{
return _distance;
}
}
string _venuename;
public string venuename
{
set
{
if (_venuename != value)
{
_venuename = value;
OnPropertyChanged("venuename");
}
}
get
{
return _venuename;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And you layout have some errors, I change of them.
<ContentPage.Content>
<StackLayout Margin="10">
<Entry x:Name="experienceEntry"
Text="{Binding Experience, Mode=TwoWay}"
Margin="-5">
</Entry>
<Label x:Name="venuename"
Text="{Binding SelectedPost.venuename, Mode=TwoWay}"
FontAttributes="Bold"
/>
<Label x:Name="categoryname"
Text="{Binding SelectedPost.categoryname, Mode=TwoWay}"/>
<Label x:Name="address"
Text="{Binding SelectedPost.address, Mode=TwoWay}"/>
<Label x:Name="coordinatelabel"
Text="{Binding SelectedPost.coordinates, Mode=TwoWay}"/>
<Label x:Name="distance"
Text="{Binding SelectedPost.distance, Mode=TwoWay, StringFormat='{0:0}'}"/>
<Button Text="Update"
x:Name="updateButton"
Command="{Binding UpdatePostCommand}"
CommandParameter="{Binding SelectedPost}" />
</StackLayout>
</ContentPage.Content>
Here is my layout background code.
public MainPage()
{
InitializeComponent();
Post selectedPost=new Post() {
address= "address",
categoryname= "categoryname",
distance= "21",
venuename= "venuename" };
PostDetailViewModel viewModel = new PostDetailViewModel(selectedPost);
BindingContext = viewModel;
}
}
Is there some type of OnObjectChanged like method that could be used? I know of the ObservableCollection when dealing with collections.
In your PostDetailViewModel it is no need to achieve that. If you want to use ObservableCollection to add Post , it achieve the INotifyPropertyChanged, If you Post items will increate or decrease, it will change automatically, But if value of Post will be changed, it will not change, you have to achieve the INotifyPropertyChanged in your Post model.
Here is my running GIF.Normally, it have some default value, if I enter the value in the Entry, this value of post will be changed, if I click the Button, the value of post will be changed to another value
Here is a helpful article about it, you can refer to it.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm

Changing protection level of newly created object?

Alright, so I have luck of running into a lot of basic problems. I can't figure a way around this particular issue.
This piece of code needs to access "_Player.Name" property of object created in "MainWindow" class.
Edit: Putting up the whole code this time. Here's the Code_Behind where the string is.
public class Code_Behind
{
private static string _Name = "Default";
public class Player
{
public void setName(string name) //Ignore this part, was trying to find a work around here
{
_Name = name;
}
public string Name
{
get { return _Name; }
set
{
_Name = value;
}
}
}
//contentControl is used to store Content properties
//UI elements are bound to Content properties to efficiently change their Content
public class contentControl : INotifyPropertyChanged
{
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public void setEvent(string Event)
{
textBoxContent = Event;
}
public void addEvent(string Event)
{
textBoxContent +="\n" + Event;
}
public class Events
{
public string EV001 = String.Format("\"Greetings {0}. What can I do for you today?\"", window.PlayerName);
}
}
And here is the MainWindow one:
public partial class MainWindow : Window
{
Code_Behind.contentControl cC = new Code_Behind.contentControl();
Code_Behind.contentControl.Events Events = new Code_Behind.contentControl.Events();
Code_Behind.Player _Player = new Code_Behind.Player();
public string GetPlayerName()
{
return _Player.Name;
}
public static string _name = "null";
public MainWindow()
{
this.DataContext = cC;
InitializeComponent();
}
public string GetPlayerName()
{
return _Player.Name
}
Create a method in your MainWindow class. After that you call this method.
public string EV001 = String.Format("\"Greetings {0}. What can I do for you today?\"",
window.GetPlayerName());
You can do it with property too if you want.
public string PlayerName
{
get { return _Player.Name; };
}
The bigger problem you have here is not about accessibility, but not understanding the difference between a class and an object.
MainWindow is a class. It does not represent any specific window. Think of a class like a recipe to create objects. If you had a chocolate chip cookie recipe, you don't eat the recipe, you eat a specific cookie or cookies baked following that recipe.
Your other class first needs to know which specific window you are trying to get the player name from. It needs a reference to a particular MainWindow object.
It looks like you're trying write something like a viewmodel: You've got a player, he has a name, and there's a collection of strings that you think of as "events". I don't understand what the "events" are meant to be, but I implemented my best guess at what I think you seem to be trying to do.
As for this:
public class Events
{
public string EV001 = String.Format("\"Greetings {0}. What can I do for you today?\"", window.PlayerName);
}
I guess you created an instance of MainWindow somewhere, and called it window, but it's defined someplace where it's "out of scope" for that line of code. By analogy, you can't see anything that's behind the next hill, only stuff that's in the valley you're standing in. That's roughly (very roughly, sorry) kind of what scope is about.
But let's move on to my guess at what you're trying to do. This builds, runs, and works. Any questions at all, fire away.
ViewModels.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Player
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class MainViewModel : ViewModelBase
{
#region Player Property
private PlayerViewModel _player = default(PlayerViewModel);
public PlayerViewModel Player
{
get { return _player; }
set
{
if (value != _player)
{
_player = value;
OnPropertyChanged(nameof(Player));
// Change the player for all the existing events.
foreach (var e in Events)
{
e.Player = Player;
}
}
}
}
#endregion Player Property
private ObservableCollection<Event> _events = new ObservableCollection<Event>();
public ObservableCollection<Event> Events
{
get { return _events; }
private set
{
if (value != _events)
{
_events = value;
OnPropertyChanged(nameof(Events));
}
}
}
#region Event Methods
// This is a BIG guess as to what you're trying to do.
public void AddGreeting()
{
// Player is "in scope" because Player is a property of this class.
if (Player == null)
{
throw new Exception("Player is null. You can't greet a player who's not there.");
}
Events.Add(new Event("\"Greetings {0}. What can I do for you today?\"", Player));
}
#endregion Event Methods
}
public class Employee : ViewModelBase
{
#region DisplayLtdOccupationId Property
private bool _displayLtdOccupationId = default(bool);
public bool DisplayLtdOccupationId
{
get { return _displayLtdOccupationId; }
set
{
if (value != _displayLtdOccupationId)
{
_displayLtdOccupationId = value;
OnPropertyChanged(nameof(DisplayLtdOccupationId));
}
}
}
#endregion DisplayLtdOccupationId Property
}
public class Event : ViewModelBase
{
public Event(String format, PlayerViewModel player)
{
_format = format;
Player = player;
}
private String _format = "";
public String Message
{
get { return String.Format(_format, Player.Name); }
}
#region Player Property
private PlayerViewModel _player = default(PlayerViewModel);
public PlayerViewModel Player
{
get { return _player; }
set
{
if (value != _player)
{
_player = value;
OnPropertyChanged(nameof(Player));
// When player changes, his name changes, so that
// means the value of Message will change.
OnPropertyChanged(nameof(Message));
if (_player != null)
{
_player.PropertyChanged += _player_PropertyChanged;
}
}
}
}
private void _player_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(PlayerViewModel.Name):
OnPropertyChanged(nameof(Message));
break;
}
}
#endregion Player Property
}
public class PlayerViewModel : ViewModelBase
{
private String _name = default(String);
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
}
}
MainWindow.xaml.cs
using System.Windows;
namespace Player
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel = new MainViewModel();
ViewModel.Player = new PlayerViewModel() { Name = "Ivan the Terrible" };
}
// Just here as a convenience, and to make sure we don't give the DataContext
// the wrong kind of viewmodel.
public MainViewModel ViewModel
{
set { DataContext = value; }
get { return DataContext as MainViewModel; }
}
private void Greeting_Click(object sender, RoutedEventArgs e)
{
ViewModel.AddGreeting();
}
}
}
MainWindow.xaml
<Window
x:Class="Player.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Player"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical">
<WrapPanel>
<Button x:Name="Greeting" Content="Greeting" Click="Greeting_Click" />
<Label>Name: </Label>
<TextBox Text="{Binding Player.Name}" Width="120" />
</WrapPanel>
<ListBox
ItemsSource="{Binding Events}"
DisplayMemberPath="Message"
>
</ListBox>
</StackPanel>
</Grid>
</Window>
You can change the set of Name to be private, but still allow the outside world to read the property with the get.
public string Name { get; private set; } = "Default";
This should give you the functionallity desired without the need to create a new GetName() method.

UWP MVVM Data Binding for dummies (textbox.text from String)

Well, having a go at MVVM with UWP template 10. I have read many pages, and although everyone tries to say its really easy, I still can't make it work.
To put it into context, OCR is being run on an image, and I would like the text to be displayed in textbox automatically.
Here is my Model:
public class TextProcessing
{
private string _ocrText;
public string OcrText
{
get { return _ocrText; }
set
{
_ocrText = value;
}
}
}
Here is my ViewModel:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextProcessing _ocrTextVM;
public ScanPageViewModel()
{
_ocrTextVM = new TextProcessing();
}
public TextProcessing OcrTextVM
{
get { return _ocrTextVM; }
set {
_ocrTextVM = value;
this.OnPropertyChanged("OcrTextVM");
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Here is my View:
<TextBox x:Name="rtbOcr"
Text="{Binding OcrTextVM.OcrText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Firstly, that is not working. Could someone try to show where I am going wrong?
Then, the data is coming from a Services file, how would the Services update the value? What would be the correct code?
Thanks in advance.
Following code is cite from code.msdn (How to achieve MVVM design patterns in UWP), it will be helpful for you:
Check you code step by step.
1.ViewModel implemented interface INotifyPropertyChanged,and in property set method invoked PropertyChanged, like this:
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _productName;
public string ProductName
{
get { return _productName; }
set
{
_productName = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(ProductName)));
}
}
}
}
2.Initialize you ViewMode in you page, and set DataContext as the ViewMode, like this:
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; } = new MainPageViewModel();
public MainPage()
{
...
this.DataContext = ViewModel;
}
}
3.In you xaml, binding data from viewMode, like this:
<TextBox Text="{Binding Path=ProductName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="ProductNameTextBox" TextChanged="ProductNameTextBox_TextChanged" />
Your OnPropertyChanged call on OcrTextVM isn't actually called in your case, since you set the value in the constructor to its backing field and bypass the property.
If you set the value via the property, it should work:
public ScanPageViewModel()
{
OcrTextVM = new TextProcessing();
}
Of course your view needs to know that ScanPageViewModel is its DataContext. Easiest way to do it is in the constructor of the code-behind of your view:
public OcrView()
{
DataContext = new ScanPageViewModel();
InitializeComponent();
}
Assuming your OCR service is returning a new TextProcessing object on usage, setting the property of OcrTextVM should suffice:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
//...
private void GetOcrFromService()
{
//...
TextProcessing value = OcrService.Get();
OcrTextVM = value;
}
}
On a note, the OcrTextVM name doesn't really reflect what the property is doing, since it doesn't look like it's a viewmodel. Consider renaming it.
Actually, it is very easy once I manage to understand. Here is the code needed to update a TextBox.Text
In the Models:
public class DisplayText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
In the XAML file:
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... />
In the ViewModels:
private DisplayText _helper = new DisplayText();
public DisplayText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
Then any mod from the ViewModels:
Helper.Text = "Whatever text, or method returning a string";

Categories

Resources