I am just getting started with MVVM so apologies if I've done something really stupid. I tried writing a very simple test to see if I could remember everything, and for the life of me I can't see why its not working.
In my view I have a textBox where its text property is bound to a value in the ViewModel. Then when pressing a button the value should be altered and the textBox update.
I can see the value does alter (I have added a MessageBox.Show() line in the buttom press command) however the textBox does not update.
I assume that this means I have not properly implemented the INotifyPropertyChanged event properly but am unable to see my mistake.
Could anyone point me in the right direction?
Here is the code:
View
<Window x:Class="Mvvm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<TextBox Height="40" Width="200" Text="{Binding helloWorld.Message, UpdateSourceTrigger=PropertyChanged}"/>
<Button Command="{Binding UpdateTimeCommand}">Update</Button>
</StackPanel>
</Window>
Behind View
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel.MainWindowViewModel();
}
}
ViewModel
namespace Mvvm.ViewModel
{
internal class MainWindowViewModel
{
private HelloWorld _helloWorld;
/// <summary>
/// Creates a new instance of the ViewModel Class
/// </summary>
public MainWindowViewModel()
{
_helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
UpdateTimeCommand = new Commands.UpdateTimeCommand(this);
}
/// <summary>
/// Gets the HellowWorld instance
/// </summary>
public HelloWorld helloWorld
{
get
{
return _helloWorld;
}
set
{
_helloWorld = value;
}
}
/// <summary>
/// Updates the time shown in the helloWorld
/// </summary>
public void UpdateTime()
{
helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
}
public ICommand UpdateTimeCommand
{
get;
private set;
}
}
Model
namespace Mvvm.Model
{
class HelloWorld : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public HelloWorld(string helloWorldMessage)
{
Message = "Hello World! " + helloWorldMessage;
}
private string _Message;
public string Message
{
get
{
return _Message;
}
set
{
_Message = value;
OnPropertyChanged("Message");
}
}
private void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
}
Commands
namespace Mvvm.Commands
{
internal class UpdateTimeCommand : ICommand
{
private ViewModel.MainWindowViewModel _viewModel;
public UpdateTimeCommand(ViewModel.MainWindowViewModel viewModel)
{
_viewModel = viewModel;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_viewModel.UpdateTime();
}
}
}
Sorry for such a long post and it being a spot my mistake post but I've looked at it for so long and I don't know what I'm doing wrong
Thanks!
The Problem that you have is that you are changing the wrong Property. Instead of changing the HelloWorld.Message Property, you are changing MainWindowViewModel.HelloWorld property. Your code will work OK if you change this line:
public void UpdateTime()
{
helloWorld = new HelloWorld("The time is " + DateTime.Now.ToString("HH:mm:ss"));
}
For this one
public void UpdateTime()
{
helloWorld.Message = "The time is " + DateTime.Now.ToString("HH:mm:ss");
}
If you want to keep your original code, then you need to implement INotifyPropertyChanged for your ViewModel, and rise the event when you change helloWorld object.
Hope this helps
I think you need to implement PropertyChanged notification on your ViewModel. You are creating a new HelloWorld in the UpdateTime method, but the UI doesn't know it.
Edit
I have a ViewModel base class which I derive all of my ViewModels from. It implements INotifyPropertyChanged, and has references to my relay command classes, and some other common stuff. I recommend always having INotifyPropertyChanged implemented on the ViewModel. The ViewModel is there to expose data to the UI, and it cant do that for data that changes without that interface.
i think your ViewModel needs to implement INotifyPropertyChanged too,
or you can set the DataContext before you call InitializeComponents(), if you do that you should change your code to NOT create a new instance every update like Agustin Meriles said.
i think you mistake Model and VM: Model is MainWindowViewModel and VM is HelloWorld
In your VM (class HelloWorld ) you need use your model
So, your classes will look like:
using System.ComponentModel;
namespace WpfApplication1
{
public sealed class TextVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextInfo _info;
public TextVM()
{
_info = new TextInfo();
}
public string MyText
{
get { return _info.MyText; }
set
{
_info.MyText = value;
OnPropertyChanged("MyText");
}
}
private void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
}
using System;
namespace WpfApplication1
{
public sealed class TextInfo
{
public TextInfo()
{
MyText = String.Empty;
}
public string MyText { get; set; }
}
}
inset inside your ICommands
Related
I am writing a WPF application to learn the MVVM Design Pattern. I am fairly new to C# and WPF.
I am trying to pass some context when switching ViewModels, that then gets used in an ICommand implementation to call a method. But the ICommand won't update after receiving the context.
Basically I create an instance of an ICommand, which a button binds to and then (when passing the context) I create another instance that replaces it.
My question then: is there a way to rebind a command binding or is the state it had at the time of intialization unmodifiable.
What I'm trying to accomplish in code:
Command.cs
public class Command : ICommand
{
public Command(Action action) => this.action = action;
public event EventHandler CanExecuteChanged;
public virtual bool CanExecute(object parameter) => true;
public virtual void Execute(object parameter) => action();
Action action;
}
ObservableObject.cs
public abstract class ObservableObject : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
FooModel.cs
public class FooModel
{
public int Number => 10;
}
BarModel.cs
public class BarModel
{
public int Number { get; set; }
}
BarViewModel.cs
public class BarViewModel : ObservableObject
{
public BarViewModel()
{
Bar = new BarModel();
BtnCommand = new Command(Reset);
}
public void Receive(object state)
{
if (state is FooModel foo)
{
Counter = Bar.Number = foo.Number;
// this won't reset the number to 10
BtnCommand = new Command(Reset);
// neither will this, why?
Reset();
}
}
public void Reset() => Counter = Bar.Number;
int counter;
public int Counter
{
get => counter;
set
{
counter = value;
OnNotifyPropertyChanged(nameof(Counter));
}
}
Command btnCommand;
public Command BtnCommand
{
get => btnCommand;
set
{
btnCommand = value;
OnNotifyPropertyChanged(nameof(BtnCommand));
}
}
BarModel Bar { get; private set; }
}
BarView.xaml
<UserControl
<! ... namespaces and such -->
>
<UserControl.DataContext>
<local:BarViewModel />
</UserControl.DataContext>
<Grid>
<Button Content="Click" Command="{Binding BtnCommand}"/>
</Grid>
</UserControl>
The Receive Method is invoked after creating the BarViewModel and it passes an instance of FooModel. When I set a breakpoint inside the if (state ...) block it says that the Bar.Number field is 10, but when it leaves the scope it's back to 0. I get a feeling that this is how it's supposed to work, but how can I accomplish the update of the Command context?
I tried to create a MCVE of my project, here's the link to dropbox. It's a VS 2017 Project using .NET 4.5.2
Screenshot of code in MCVE:
EDIT: changed fileupload URL
EDIT2: added screenshot
EDIT3: updated code
EDIT4: changed fileupload to dropbox
You are creating a new instance of the BarViewModel in your Bar view. Remove this XAML markup:
<UserControl.DataContext>
<local:BarViewModel />
</UserControl.DataContext>
Then the commands that you create in your Receive method should be invoked as expected.
Tried a lot of stuff, still doesn't work. Binding on the two TextBlocks don't work. Used INotifyPropertyChanged interface much like this code to no avail.
Code:
MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ClockWatcher" xmlns:System="clr-namespace:System;assembly=mscorlib"
x:Name="clockWatcherWindow"
x:Class="ClockWatcher.MainWindow"
Title="Clock Watcher" Height="554" Width="949"
KeyDown="KeysDown" Focusable="True" Closing="SaveSession"
DataContext="{Binding SM, RelativeSource={RelativeSource Self}}">
<TextBlock x:Name="programStartBlock" Text="{Binding StartTime, BindsDirectlyToSource=True, FallbackValue=Binding sucks so much!!!, StringFormat=ProgramStarted: \{0\}, TargetNullValue=This thing is null}" Padding="{DynamicResource labelPadding}" FontSize="{DynamicResource fontSize}"/>
<TextBlock x:Name="totalTimeLabel" Text="{Binding SM.currentSession.TotalTime, StringFormat=Total Time: \{0\}}" Padding="{DynamicResource labelPadding}" FontSize="{DynamicResource fontSize}"/>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private const string SESSION_FILENAME = "SessionFiles.xml";
/// <summary>
/// Represents, during selection mode, which TimeEntry is currently selected.
/// </summary>
public SessionManager SM { get; private set; }
public MainWindow()
{
InitializeComponent();
SM = new SessionManager();
SM.newAddedCommentEvent += currentTimeEntry_newComment;
SM.timeEntryDeletedEvent += currentTimeEntry_delete;
SM.commentEntryDeletedEvent += entry_delete;
}
}
SessionManager.cs:
public class SessionManager : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NonSerialized]
private DateTime _dtStartTime;
private Session current_session;
#region Properties
public DateTime StartTime
{
get
{
return _dtStartTime;
}
private set
{
if (_dtStartTime != value)
{
_dtStartTime = value;
OnPropertyChanged("StartTime");
}
}
}
public Session CurrentSession
{
get
{
return current_session;
}
set
{
if (current_session != value)
{
OnPropertyChanged("CurrentSession");
current_session = value;
}
}
}
#endregion
public SessionManager()
{
_dtStartTime = DateTime.Now;
}
private void OnPropertyChanged([CallerMemberName] string member_name = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(member_name));
}
}
}
Session.cs:
public class Session : INotifyPropertyChanged
{
private TimeSpan total_time;
public DateTime creationDate { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
public TimeSpan TotalTime
{
get
{
return total_time;
}
set
{
if (total_time != value)
{
OnPropertyChanged("TotalTime");
total_time = value;
}
}
}
public Session()
{
creationDate = DateTime.Now;
}
private void OnPropertyChanged([CallerMemberName] string member_name = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(member_name));
}
}
}
In first TextBlock, instead of SM.StartTime, write only StartTime.
Remove ElementName from first TB.
Make CurrentSession public property, Your currentSession is private now.
In your SessionManager ctor, current_session = new Session();
Remove DataContext from XAML, use this.DataContext = SM; in your window contructor.
If you want to use DataContext in XAML,
<Window.DataContext>
<local:SessionManager />
</Window.DataContext>
The marked correct answer is definitely the better way to do it, but I just wanted to answer explaining more in detail why what you posted didn't work.
The issue is that when you wrote DataContext={Binding SM, RelativeSource={RelativeSource Self} in your MainWindow.xaml, the binding was evaluted before your line SM = new SessionManager(); was executed in your MainWindow.xaml.cs constructor.
You can see this in effect if you changed your getter for SM to:
public SessionManager SM
{
get { return new SessionManager();}
}
This basically ensures that when WPF evaluates your binding, it'll get an actual object for your SM property instead of null.
Just thought perhaps this will help understanding and reduce frustration next time :). The way you asked your question, you technically needed to implement INotifyPropertyChanged on your MainWindow class, which is a big no-no.
I have a button like this:
<Button Content="Gönder" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="932,23,0,0" Height="25" Command="{Binding Path=SetTeamList}" CommandParameter="{Binding ElementName=UrlBox, Path=Text}"/>
And at the VM, i have a method
public void SetTeamList(string Url)
{
//Some things here
}
The solution is WinForms app, so i set DataContext like this:
var view = new dTeamMapperForm();
view.DataContext = new TeamMappingVM();
elementHost1.Child = view;
Nothing happens when i click the button, no error or something. I put break point to SetTeamList method and it's not executing on button click.
Edit: I have changed the whole VM, now it looks like:
class TeamMappingVM : INotifyPropertyChanged
{
public ObservableCollection<Team> TeamList { get; set; }
public ICommand SetTeamsCommand { get; internal set; }
private string _url;
public string Url
{
get { return _url; }
set
{
_url = value;
NotifyPropertyChanged("Url");
}
}
public void SetTeamList()
{
var mapper = new TeamMapper();
TeamList = new ObservableCollection<Team>(mapper.MapTeams(Url));
}
public bool CanParseTeams()
{
return !string.IsNullOrEmpty(Url);
}
public TeamMappingVM()
{
SetTeamsCommand = new RelayCommand(SetTeamList, CanParseTeams);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
The Command-Property of a Button expects you to Bind to an Property of type ICommand.
In your Case you tried to Bind to a method, which does not work.
Since you edited you post i will just post this as the answer:
XAML:
<Button Content="Gönder" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="932,23,0,0" Height="25" Command="{Binding Path=SetTeamsCommand }" CommandParameter="{Binding ElementName=UrlBox, Path=Text}"/>
class TeamMappingVM : INotifyPropertyChanged
{
public ObservableCollection<Team> TeamList { get; set; }
public ICommand SetTeamsCommand { get; internal set; }
private string _url;
public string Url
{
get { return _url; }
set
{
_url = value;
NotifyPropertyChanged("Url");
}
}
public void SetTeamList()
{
var mapper = new TeamMapper();
TeamList = new ObservableCollection<Team>(mapper.MapTeams(Url));
}
public bool CanParseTeams()
{
return !string.IsNullOrEmpty(Url);
}
public TeamMappingVM()
{
SetTeamsCommand = new RelayCommand(SetTeamList, CanParseTeams);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
As a minor side note. Which was not asked. Since you are probably Using Databinding for your URL Textbox you don't need to pass it into the method via CommandParameter, Since the URL Property of your ViewMOdel represents this textbox. You want to try to seperate the view from the logic. This is a very small issue and might not have any effect, but it sort of is a bad habit to fall into.
As Xeun pointed out, a Command is not a method but an object implementing the ICommand interface. A Command implementation look like this:
class MyCommand: ICommand
{
public bool CanExecute(object parameter)
{
return true; // if your command is "enabled" otherwhise return false
}
public void Execute(object parameter)
{
// do something usefull
}
}
In this sample you should add an instance of MyCommand to your ViewModel an
bind to it.
Please notice usually you dont code commands this way.
A command usually interact with your ViewModel (ie it invokes Model methods) and inside MyCommand you have not references to the ViewModel hosting it.
(You could create a Command which hold a reference to its ViewModel, but...) Usually inside a ViewModel you use a Relay command or a Delegate command (which are basically the same thing).
Why does my textbox fail to update when I try to update it from another class?
I've instantiated the MainWindow class in my Email class, but when I try to do
main.trending.Text += emailText;
Am I doing something wrong?
You should bind your data.
Model
public class YourData : INotifyPropertyChanged
{
private string _textBoxData;
public YourData()
{
}
public string TextBoxData
{
get { return _textBoxData; }
set
{
_textBoxData = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("TextBoxData");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
XAML Binding
Set data context in Codebehind
this.DataContext = YourData;
Bind Property
<TextBox Text="{Binding Path=Name2}"/>
See #sa_ddam213 comment. Dont do something like MainWindow main = new MainWindow(); inside Email class. Instead, pass the MainWindow object you already have.
Following codes will work:
public class MainWindow
{
public void MethodWhereYouCreateEmailClass()
{
Email email = new Email;
email.Main = this;
}
}
public class Email
{
public MainWindow main;
public void MethodWhereYouSetTrendingText()
{
main.trending.Text += emailText;
}
}
But I dont say that is best practice. I just try to keep it close to your existing code i guess.
I'm new to WPF, so there's probably something basic I'm missing here. I have an application that looks like this:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test Application" Height="647" Width="723" Background="#88B0FF">
<DockPanel Name="MainDock">
<Button DockPanel.Dock="Top" Margin="5,0,5,0" x:Name="PingButton" Click="PingButton_OnClick">Ping</Button>
<TextBox Text="{Binding Path=Output}" />
</DockPanel>
</Window>
The code-behind is like this:
public partial class MainWindow : Window
{
private Model _applicationModel = new Model();
public Model ApplicationModel {
get { return _applicationModel; }
set { _applicationModel = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = ApplicationModel;
ApplicationModel.Output = "Not clicked";
}
private void PingButton_OnClick(object sender, RoutedEventArgs e)
{
ApplicationModel.Output = "Clicked";
}
}
I have a small class called Model that implements INotifyPropertyChanged.
public class Model : INotifyPropertyChanged
{
public string Output { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I run this application, and the text box displays the text "Not clicked". When I click the button, I would expect that the text would change. It does not. The "ApplicationModel" object is updated, and this is reflected in the DataContext; I have a breakpoint in the OnPropertyChanged() method, however, and it appears that it's never being called.
What am I doing wrong?
OnPropertyChanged() isn't being called because you're not calling it.
There is no special magic that wires up calls to OnPropertyChanged by itself, so you need to do it yourself.
Specifically, you should modify your Output property to call it when it changes (and it wouldn't hurt to do the same for your ApplicationModel property:
private string output;
public string Output
{
get { return output; }
set
{
if (output != value)
{
output = value;
OnPropertyChanged("Output");
}
}
}
If you're targeting .NET 4.5 you can utilize the CallerMemberName attribute to reduce boilerplate code; This article explains how to do so. Then you'll have something like this:
private string output;
public string Output
{
get { return output; }
set { SetProperty(ref output, value); }
}
If you're using .NET 4.0 or below, you can use expression trees, as described in this answer.