Okay, so I've been attempting to learn a bit of MVVM. It's been only three days and have come to a dead end. The application simply retrieves the client's external ip address and updates the new ip address in a label found in my UI. However, rather than just going the old-fashioned way of simply doing,
ipAdd.Content = getNewIp();
and ending the story end there. Rather, I decided to use some MVVM to achieve the following:
Retrieve ip address, then display it to the user in a label
Query the database and update the user's ip address
Rinse, repeat every five minutes.
The reason I chose to go more MVVM is to use UpdateSourceTrigger and UpdateCommand. Essentially, when the ip address gets updated, I want to do something with it (as mentioned above). I've seen in many tutorials where UpdateCommand is used on buttons and UpdateSourceTrigger used on input boxes, but nothing that shows how to do it with labels, yet it seems that it should be tied in to the same concept; it might not be explicity a button that is being clicked, but some back code that executes and performs the same task as a button. For starters, here's what I have so far:
Model
namespace IPdevices
{
class IP : INotifyPropertyChanged
{
private string _add;
public string add
{
get
{
return _add;
}
set
{
_add = value;
OnPropertyChange("add");
}
}
private void OnPropertyChange(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
ViewModel
namespace IPdevices
{
class IPViewModel
{
private IP _ip;
public IPViewModel()
{
UpdateCommand = new IPUpdateCommand(this);
}
public ICommand UpdateCommand
{
get; private set;
}
public bool CanUpdate
{
get
{
return true; // just say yes for now
}
}
public IP IP
{
get
{
return _ip;
}
set
{
_ip = value;
}
}
public void updateClientIp()
{
Console.WriteLine("_updating client ip and query database");
}
}
}
ICommand
namespace IPdevices
{
class IPUpdateCommand : ICommand
{
private IPViewModel _viewModel;
public IPUpdateCommand(IPViewModel vm)
{
_viewModel = vm;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return _viewModel.CanUpdate;
}
public void Execute(object parameter)
{
_viewModel.updateClientIp();
}
}
}
MainWindow
public Main()
{
InitializeComponent();
ipAdd.DataContext = new IPViewModel();
}
View
<Label x:Name="ipAdd" Content="{Binding Path=IP.add}"/>
What I'm trying to achieve here can easily be done without going MVVM (perhaps overkill as well) and simply calling a few helper classes to update the user's ip on the db side, but for the sake of getting used to doing things MVVM, I tried incorporating the way I did, though with no luck.
Is there any way to do this, or even better, should it be done this way?
Related
When I searched,I found,how to bind values from viewmodel to view but not viewmodel to viewmodel.can anyone help me to do that. what i need is to pass Authentication to other viewmodel.I am new in the MVVM world so please give me more detail.
my ViewModel look like this
public class ModelView_Authentication : INotifyPropertyChanged
{
//Binding authentication
private Authentication _authentication;
public Authentication authentication
{
get { return _authentication; }
set
{
_authentication = value;
NotifayPropertyChanged("_authentication");
}
}
//Command Button
public ModelView_Authentication()
{
authentication = new Authentication();
ButtonCommand = new ViewModdelCommand(exeMethode, canexeMethode);
}
public ICommand ButtonCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private bool canexeMethode(Object param)
{
return true;
}
//run this Command Onclick Button
private void exeMethode(Object param)
{
}
protected void NotifayPropertyChanged(string s)
{
PropertyChangedEventHandler pc = PropertyChanged;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(s));
}
}
//Run Assync Login
public static async Task<string> main(Authentication authentication)
{
var tocken = await Login.GetConnection(authentication);
return tocken.ToString();
}
}
need is to pass Authentication to other viewmodel
Your main ViewModel adheres to INotifyPropertyChanged, you can have your other VMs subscribe to the notification process of the main VM and acquire changes to specific properties as needed.
Just have a reference to the main VM, it is as easy as that. Where the VMs get their references, that process is up to you.
A good place is on App class. Since the App class is known throughout each of the namespaces, setup up a static property on it, set it after the main VM is created, and then access the it as needed.
public static ModelView_Authentication AuthVM { get; set; }
the access such as
var mainVM = App.AuthVM;
I want to show the stream data to textbox in real time. But the textbox doesn't updated even the stream data has updated. I don't know what is wrong.
Here is my XAML code.
<TextBox Text="{Binding Path = marketPrice}" HorizontalAlignment="Left"/>
And this is View Model code.
public class OrderTestViewModel : INotifyPropertyChanged
{
public QuotesDataSource DataSource;
public string _marketPrice => DataSource.SymbolPrice;
public string marketPrice
{
get { return _marketPrice; }
set
{
RaisePropertyChanged("marketPrice");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I checked the marketPrice is updated real time.
And the last is hidden code.
public partial class OrderTest : UserControl
{
OrderTestViewModel model = new OrderTestViewModel();
public OrderTest()
{
InitializeComponent();
DataContext = model;
}
}
Please help me.
It seems your marketPrice setter never update the value of _marketPrice (which will always show the same value.
Would you want something like :
public string _marketPrice = DataSource.SymbolPrice;
public string marketPrice
{
get { return _marketPrice; }
set
{
if (_marketPrice != value)
{
_marketPrice = value;
RaisePropertyChanged("marketPrice");
}
}
}
I want to update new value in UI when DataSource.SymbolPrice is updated. DataSource.SymbolPrice is updated periodly
Then you should bind directly to the SymbolPrice property and implement INotifyPropertyChanged and raise the PropertyChanged event in the QuotesDataSource class:
public class OrderTestViewModel
{
public QuotesDataSource DataSource;
public string marketPrice => DataSource.SymbolPrice;
}
Obviously some object must tell the UI when there is a update and this is the responsibility of the source object.
The view model cannot be supposed to know when the price is changed in the QuotesDataSource unless the latter tells it somehow, for example by raising an event.
I have a WPF project and a Console one, the point of the WPF is to be the frontend UI and the console application is the logic that does the actual work.
In my backend I have a class with a method that does the work.
public static class BackendClass
{
public static void DoWork(ref string output)
{
//actual work
}
}
From the MVVM frontend my view model starts a task for this method and I want to be able to show status messages on the frontend about it. Things like "Started work.", "Doing so-and-so.", "Finished." and etc.
The code in my view model is:
class ViewModel : INotifyPropertyChanged
{
static string backendOutput;
public string BackendOutput
{
get => backendOutput;
set
{
if (backendOutput != value)
{
backendOutput = value;
OnPropertyChanged("BackendOutput");
}
}
}
public RelayCommand ExecuteCommand { get; private set; }
Task executionTask;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
executionTask = new Task(() => BackendClass.DoWork(ref BackendOutput));
}
void OnExecute()
{
executionTask.Start();
ExecuteCommand.RaiseCanExecuteChanged();
}
bool CanExecute()
{
return (executionTask.Status != TaskStatus.Running &&
executionTask.Status != TaskStatus.WaitingToRun);
}
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The "BackendOutput" property is data binded to a text block in the WPF window.
I was thinking of passing the "BackendOutput" property so the "DoWork" method can append its status messages to it, thus raising the changed event, updating the frontend.
However if I try to assign it outside of the constructor I get the error that a property can't be a field initializer or something like that and in this case I get "property can't be passed as a ref parameter".
So how should I alert the frontend of what status messages the back is pumping?
ViewModel communicates with View via PropertyChanged event. So Model also can have an event. ViewModel subscribes to that event, updates property with event data, View gets updated.
Events are kind of protected delegates. So as a first step try to introduce a delegate:
public static void DoWork(Action<string> notifier)
{
notifier("output value");
}
and
executionTask = new Task(() => BackendClass.DoWork(str => { BackendOutput = str; }));
I have created various properties inside of a User Control, and have had great success with accessing and editing them. I'm now trying to set up events for a number of these to be raised when one of these properties is changed. I have tried the MSDN example code for doing this (see here), but it is giving me this error when I try to build the solution:
Severity Code Description Project File Line Suppression State
Error CS0079 The event 'AbilityScoreDisplay.AbilityTitleChanged' can only appear on the left hand side of += or -= DnD Character Sheet C:\Users\bradley beasley\Documents\Visual Studio 2019\Projects\DnD Character Sheet\DnD Character Sheet\AbilityScoreDisplay.Designer.cs 199 Active
Another issue that I am having is that I am struggling to figure out how to get that event to appear in the Visual Studio 2019 Designer Properties window.
Here is the code that I have added to the designer file:
namespace DnD_Character_Sheet
{
partial class AbilityScoreDisplay : UserControl
{
public string AbilityTitle
{
get
{
return AbiltyTitleLabel.Text;
}
set
{
AbiltyTitleLabel.Text = value;
Invalidate();
}
}
public int AbilityModifier
{
get
{
return Convert.ToInt32(AbilityModifierTextBox.Text);
}
private set
{
if (value >= 0) AbilityModifierTextBox.Text = String.Format("+{0}", value);
else AbilityModifierTextBox.Text = value.ToString();
Invalidate();
}
}
public int AbilityScore
{
get
{
return Convert.ToInt32(AbilityScoreLabel.Text);
}
set
{
AbilityModifier = (int)(Math.Floor((double)(value) / 2)) - 5;
Invalidate();
}
}
private EventHandler onAbilityTitleChanged { get; set; }
private EventHandler onAbilityScoreChanged { get; set; }
public event EventHandler AbilityTitleChanged
{
add
{
onAbilityTitleChanged += value;
}
remove
{
onAbilityTitleChanged -= value;
}
}
public event EventHandler AbilityScoreChanged
{
add
{
onAbilityScoreChanged += value;
}
remove
{
onAbilityScoreChanged -= value;
}
}
protected virtual void OnAbilityTitleChanged(EventArgs e)
{
AbilityTitleChanged?.Invoke(this, e);
}
protected virtual void OnAbilityScoreChanged(EventArgs e)
{
AbilityScoreChanged?.Invoke(this, e);
}
}
}
The aim is to enable an event to be raised whenever a property is changed so that it can do other stuff elsewhere in the form that the controls will be in. I'm fairly certain that I am missing some very important stuff, or that my code is not that effective at all, but I am learning this kind of code for the first time, and I have tried many different things that have just not worked.
Any help at all would be greatly appreciated :)
I think you are confusing a few concepts. Let's do it step by step.
First, you need to be able to track event handlers:
private EventHandler _onAbilityTitleChanged;
You expose this event through a public property:
public event EventHandler AbilityTitleChanged
{
add
{
_onAbilityTitleChanged += value;
}
remove
{
_onAbilityTitleChanged -= value;
}
}
Finally, you need to fire the event so that all subscribed handlers can react to it. You can do so when the title changes (setter):
public string AbilityTitle
{
get
{
return AbiltyTitleLabel.Text;
}
set
{
AbiltyTitleLabel.Text = value;
//Raising the event!
_onAbilityTitleChanged?.Invoke(this, new EventArgs());
}
}
Other classes can then subscribe to your event:
var control = new AbilityScoreDisplay();
control.AbilityTitleChanged += SomeHandlerForWhenTitleChanges;
private void SomeHandlerForWhenTitleChanges(object sender, EventArgs e)
{
//....
}
You might want to read up a bit on the INotifyPropertyChanged interface as well.
You typically do this by implementing INotifyPropertyChanged. This allows you to use one single event for all the properties. The property name is passed in the event arguments.
partial class AbilityScoreDisplay : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...
}
In the properties do this (with AbilityModifier as an example):
private int _abilityModifier;
public int AbilityModifier
{
get { return _abilityModifier; }
private set {
if (value != _abilityModifier) {
_abilityModifier = value;
AbilityModifierTextBox.Text = value >= 0
? String.Format("+{0}", value)
: value.ToString();
OnPropertyChanged(nameof(AbilityModifier));
}
}
}
Assuming this event handler
private void ScoreDisplay_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
You can subscribe the event with
PropertyChanged += ScoreDisplay_PropertyChanged;
You need to use the add/remove syntax only in rare cases. Typically, when you create your own event store, because you have a lot of events and don't want to consume space for unsubscribed events.
You can use INotifyPropertyChanged together with data binding to immediately update the UI when changes are made to the data. To do this you would create a class with properties and the INotifyPropertyChanged implementation. In the form you then assign an instance of this class to the DataSource of a BindingSource. The controls are then bound to this BindingSource.
Then you can drop all the code used to read from or to write to text boxes or labels etc., as the binding mechanism does it automatically for you.
I am running into an issue that I have found on some similar post, however, they are not quite the same and I am not quite sure how to apply it to my scenario. They may or may not be the same as my case. So, I am posting my own question here hopefully, I will get an answer to my specific scenario.
Basically, I have a window form with a bunch of controls. I would like to have the ability to bind their Enabled property to a Boolean variable that I set so that they can be enable or disable to my discretion.
public partial class MyUI : Form
{
private int _myID;
public int myID
{
get
{
return _myID;;
}
set
{
if (value!=null)
{
_bEnable = true;
}
}
}
private bool _bEnable = false;
public bool isEnabled
{
get { return _bEnable; }
set { _bEnable = value; }
}
public myUI()
{
InitializeComponent();
}
public void EnableControls()
{
if (_bEnable)
{
ctl1.Enabled = true;
ctl2.Enabled = true;
......
ctl5.Enabled = true;
}
else
{
ctl1.Enabled = false;
ctl2.Enabled = false;
......
ctl5.Enabled = false;
}
}
}
}
The method EnableControls above would do what I need but it may not be the best approach. I prefer to have ctrl1..5 be bound to my variable _bEnable. The variable will change depending on one field users enter, if the value in the field exists in the database, then other controls will be enabled for user to update otherwise they will be disabled.
I have found a very similar question here
but the data is bound to the text field. How do I get rid of the EnableControls method and bind the value of _bEnabled to the "Enabled" property in each control?
Go look into the MVVM (Model - View - ViewModel) pattern, specifically its implementation within Windows Forms. Its much easier to apply it to a WPF/Silverlight application, but you can still use it with Windows Forms without too much trouble.
To solve your problem directly, you will need to do 2 things:
Create some class that will hold your internal state (i.e. whether or not the buttons are enabled). This class must implement INotifyPropertyChanged. This will be your View Model in the MVVM pattern.
Bind an instance of the class from 1.) above to your Form. Your form is the View in the MVVM pattern.
After you have done 1 and 2 above, you can then change the state of your class (i.e. change a property representing whether a button is enabled from true to false) and the Form will be updated automatically to show this change.
The code below should be enough to get the concept working. You will need to extend it obviously, but it should be enough to get you started.
View Model
public class ViewModel : INotifyPropertyChanged
{
private bool _isDoStuffButtonEnabled;
public bool IsDoStuffButtonEnabled
{
get
{
return _isDoStuffButtonEnabled;
}
set
{
if (_isDoStuffButtonEnabled == value) return;
_isDoStuffButtonEnabled = value;
RaisePropertyChanged("IsDoStuffButtonEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View
public class View : Form
{
public Button DoStuffButton { get; set; }
public void Bind(ViewModel vm)
{
DoStuffButton.DataBindings.Add("Enabled", vm, "IsDoStuffButtonEnabled");
}
}
Usage
public class Startup
{
public ViewModel ViewModel { get; set; }
public View View { get; set; }
public void Startup()
{
ViewModel = new ViewModel();
View = new View();
View.Bind(ViewModel);
View.Show();
ViewModel.IsDoStuffButtonEnabled = true;
// Button becomes enabled on form.
// ... other stuff here.
}
}
Maybe you can try this approach: in your isEnabled property's setter method, add an if statement:
if(_bEnable) EnableControls();
else DisableControls();
And if your control names are ctl1,ctl2... etc. you can try this:
EnableControls()
{
for(int i=1; i<6;i++)
{
string controlName = "ctl" + i;
this.Controls[controlName].Enabled = true;
}
}
And apply the same logic in DisableControls
If you have more controls in future this could be more elegant.