I'm trying to update a property using a PropertyChangedEventHandler, but I think my conceptual understanding of how this works might be a bit flawed. As I'm new to WPF and silver-light.
So, let me explain, I have a property that is set to 0, but after some time a thread changes the value from 0 to 9 internally, but despite the change in value, this property never gets updated in the actual view and I don't know why! Even after I implement a PropertyChangedEventHandler there is no change, but if I log the property it shows that the value is in fact 9
So here is the snippet of code that implements PropertyChangedEventHandler:
public class CustomColumn : IColumnViewable, INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged(string propertyName)
{
Foo.log.Error(": start on property change");
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
Foo.log.Error(": end on property change");
}
public static string _total;
public string total { get { return _total; } set { _total = value; OnPropertyChanged("total"); Foo.log.Error(": property change"); } }
...
}
Here is part of my xaml:
<DataTemplate x:Key="ColumnView">
<UserControl HorizontalAlignment="Stretch">
<StackPanel HorizontalAlignment="Stretch">
...
<RichTextBox Margin="5,2,5,2">
<Paragraph>
<Run Text="{Binding Path=total, Mode=OneWay}" FontWeight="Bold" FontSize="30" />
<Run Text=" total clicks" FontWeight="Bold" />
</Paragraph>
</RichTextBox>
...
<ContentControl VerticalAlignment="Stretch" Content="{Binding Path=timeline}" ContentTemplate="{Binding Path=timelineView.ContentTemplate}" />
</StackPanel>
</UserControl>
</DataTemplate>
And I do this on initialize:
CustomColumn content = new CustomColumn();
content.total = "0";
And then I pass the object to a thread which at some point does this:
content.total = "9";
Foo.log.Error("value is "+content.total);
And the property never updates and I don't know why - any help is greatly appreciated
If I understand the details of your question, you are updating a UI bound value on a background thread. You need to make that happen on the UI thread or the change will not be visible. In one of our WPF apps random updates were disappearing until we realised this.
We do a lot of multi-threading in our Silverlight (and WPF) apps so to avoid this problem, we implemented our notify helper in a base class like the one below (other stuff trimmed out). It dispatches all notify messages on the main UI thread. Give it a try:
public class ViewModelBase : INotifyPropertyChanged
{
protected delegate void OnUiThreadDelegate();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Ensure property change is on the UI thread
this.OnUiThread(() => this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
}
protected void OnUiThread(OnUiThreadDelegate onUiThreadDelegate)
{
// Are we on the Dispatcher thread ?
if (Deployment.Current.Dispatcher.CheckAccess())
{
onUiThreadDelegate();
}
else
{
// We are not on the UI Dispatcher thread so invoke the call on it.
Deployment.Current.Dispatcher.BeginInvoke(onUiThreadDelegate);
}
}
}
Your code does not show where you make the object the DataContext of your controls, which is necessary for your bindings which do not specify another source and hence bind to the DataContext.
CustomColumn content = new CustomColumn();
content.total = "0";
Do you have any line after this where this object is passed to your view?
Related
There are tons and tons of articles around the internet about this topic, but I just can't wrap my head around it. Most articles use code behind, but I want to stick to "pure" MVVM since I try to learn it. Also, I explicitly don't want to use any other framework (MVVMlight, Ninject...). I just want to stick to what WPF has to offer. I know this got asked a lot, but what I found either was not mvvm or was not specific enough.
My task is simple: I want to see the most simple solution of opening a modal dialog, send it a string, and get a string from the dialog back upon closing it.
Therefore I set up my MainWindow.xaml with a text input field (TextBox), a button (that should open the modal dialog) and a textblock that will show the message I intend to receive from the dialog.
The dialog has a TextBlock, showing the user-input from MainWindow.xaml, and a TextBox to enter some text, and a button. You guessed it: you press the button, and the message I typed into the textfield get's returned to MainWindow.xaml. Please refer also to the images I've included - I think it's pretty self-explanatory.
MainWindow.xaml
<Window x:Class="Dialogs.MainWindow"
...
Title="First View (Main Window)" Height="240" Width="630">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Main View sayz: "/>
<TextBox Width="360" Margin="10,0,0,30"/>
</StackPanel>
<Button Content="Send to Second View" Command="{Binding SendToSecondViewCommand}" Width="200"/>
<StackPanel Orientation="Horizontal" Margin="10,30,10,10">
<TextBlock Text="Second View replies: "/>
<TextBlock Width="360"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
SecondView.xaml
<UserControl x:Class="Dialogs.SecondView"
...
d:DesignHeight="240" d:DesignWidth="630" Background="BlanchedAlmond">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="This is what First View sayz: "/>
<TextBlock Width="360"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Second View replies: "/>
<TextBox Width="360" Margin="10,0,0,30"/>
</StackPanel>
<Button Content="Reply to First View" Command="{Binding ReplyToFirstViewCommand}" Width="200"/>
</StackPanel>
</Grid>
</UserControl>
Here is how I implemented INotifyPropertyChanged (It's actually a .cs file named BaseClasses; I know it's not named properly...)
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<T>(ref T variable, T value,
[CallerMemberName] string propertyName = null)
{
variable = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And here my base class for relay commands:
public class CommandDelegateBase : ICommand
{
public delegate void ExecuteDelegate(object parameter);
public delegate bool CanExecuteDelegate(object paramerter);
private ExecuteDelegate execute;
private CanExecuteDelegate canExecute;
public CommandDelegateBase(ExecuteDelegate _execute, CanExecuteDelegate _canExecute = null)
{
execute = _execute;
canExecute = _canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object parameter)
{
execute.Invoke(parameter);
}
}
Lastly my ViewModels:
FirstViewModel:
public class FirstViewViewModel: NotifyPropertyChangedBase
{
private string _sendText;
public string SendText
{
get { return _sendText; }
set
{
_sendText = value;
OnPropertyChanged(ref _sendText, value);
}
}
public ICommand SendToSecondViewCommand { get; set; }
public FirstViewViewModel()
{
SendToSecondViewCommand = new CommandDelegateBase(SendExecuteCommand, SendCanExecuteCommand);
}
private bool SendCanExecuteCommand(object paramerter)
{
return true;
}
private void SendExecuteCommand(object parameter)
{
//Do stuff to :
// a) show the second view as modal dialog
// b) submit what I just wrote (SendText)
}
}
SecondViewModel:
public class SecondViewViewModel : NotifyPropertyChangedBase
{
private string _replyText;
public string ReplyText
{
get { return _replyText; }
set
{
_replyText = value;
OnPropertyChanged(ref _replyText, value);
}
}
public ICommand ReplyToFirstViewCommand { get; set; }
public SecondViewViewModel()
{
ReplyToFirstViewCommand = new CommandDelegateBase(ReplyExecuteCommand, ReplyCanExecuteCommand);
}
private bool ReplyCanExecuteCommand(object paramerter)
{
return true;
}
private void ReplyExecuteCommand(object parameter)
{
//Do stuff to :
// a) close the second view
// b) reply what I just wrote (ReplyText) back to First View.
}
}
I have a folder called "Models" in my solution but for the sake of simplicity it's empty.
I know there are solutions with helper classes or services - what ever pertains mvvm will do. I also do know that doing this for such a simple task as what I want is quiet "overkill", and has a lot more writing code coming with it than it would be justifyable for this purpose. But again: I'd like to learn this, and understand what I am doing.
Thank you so much in advance!
I wrote an article about this subject and provided a library and sample application. The article itself is long...because it's not a trivial topic...but causing a dialog box to appear can be as simple as this:
this.Dialogs.Add(new CustomDialogBoxViewModel()); // dialog box appears here
UPDATE: I just noticed that my MvvmDialogs library in that package is actually referencing MvvmLite. That's a vestigial remnant from when I was developing it though, the library itself doesn't need it, so you can remove the reference altogether.
Finding an MVVM pure solution to a programming problem, which may be straightforward in other contexts, is often not a simple task. However, creating a library of helper classes is a "write once, use many times" scenario, so no matter how much code is required, you don't have to reproduce it for every usage.
My preferred method for handling message dialogs in MVVM is a two part service module.
The View registers its data context (its ViewModel) with the DialogService as potentially wanting to display a dialog - the service will use the View's UI context to do so when it does.
The ViewModel calls the injected dialog service each time a dialog should be displayed. Calls to the MessageDialog service are made using the async / await pattern, rather than requiring some other form of callback in the ViewModel.
So now, displaying a MessageDialog from a ViewModel is as simple as
await _dialogService.ShowMessageAsync(this, "Hello from the dialog service.", perDialogIcon.Information, "Mvvm Dialog Service").ConfigureAwait(false);
or
var response = await _dialogService.ShowDialogAsync(this, perDialogButton.YesNo, "Do you want to continue?", perDialogIcon.Question, "Mvvm Dialog Service").ConfigureAwait(false);
I covered this in more detail on a blog post.
As an aside, your ViewModel properties look a bit wierd - you're setting the backing-field value, then passing it into your OnPropertyChanged() method where the value is set again.
In my app I have a user control with the following XAML segment
<StackPanel x:Name="stackPanel" Style="{StaticResource sPanel1}" >
<ToggleButton Style="{StaticResource tButton}">
<TextBlock Text="{Binding Note, Mode=TwoWay}" Style="{StaticResource textBlockStyle}"/>
</ToggleButton>
</StackPanel>
that 'Note' bound in the TextBlock is defined in my model as so:
private string m_Note;
public string Note
{
get { return m_Note; }
set
{
m_Note = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("m_Note"));
}
}
The 'Note' property updates when an event handler from my user control code-behind fires the event:
public void cBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
cm.Note = cBox.SelectedItem.ToString();
}
But every time I select an item from the ComboBox the UI does not update. I know that the binding is correct because when I initialize 'Note' in the model's constructor it does show it's value in the UI, and I know that 'Note' gets the cBox.SelectedItem value because I've walked through the code. What am I missing?
The binding path and mode in the View is correct. That is why you get the value on initialize. Your bound property however is not raising the correct Property Name on property changed. The UI is listening for Note but you are raising m_Note.
You need to update to
private string m_Note;
public string Note
{
get { return m_Note; }
set
{
m_Note = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Note"));
}
}
I am quiet new to programming and am currently learning C# and the MVVM pattern.
I need to code a database tool for ChiliPlants for university.
There you should be able to add a new object to an ObservableCollection.
To add a new Item to this ObservableCollection a new Window opens. It looks like this:
Window Add
I now want the two RadioBoxes to be bound to a property called "HybridSeed". Which is defined in the ViewModel:
//Public Property HybridSeed
public bool HybridSeed
{
get { return ChiliModel.HybridSeed; }
set
{
if (ChiliModel.HybridSeed == value)
return;
ChiliModel.HybridSeed = value;
OnPropertyChanged("HybridSeed");
}
}
The RadioBox part of my View looks like this:
<RadioButton Grid.Row="5" Content="Ja" Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<RadioButton Grid.Row="5" Content="Nein" Grid.Column="1" HorizontalAlignment="Left" Margin="89,10,0,0" VerticalAlignment="Top"/>
But how to bind the outcome of a user clicking on these RadioButtons to this HybridSeed Property? Important is that the outcome is a bool.
I looked up almost every entry similar to this topic, but I did not find a simple solution. Or a solution which I was able to understand with my bad coding skills :( ...
I would be very happy if you guys could help me. Please keep it simple for this newbie :)
If there is a simpler solution using a CheckBox or a ComboBox it would also be perfect. The most important thing is to have a nice user interface. Right now it only works with a TextBox where the user always has to write "True" or "False".
Solution:
I added the IsClicked Property in the "Yes" RadioButton to be bound to my boulean property with: IsClicked="{Binding HybridSeed}". Thanks to naslund for his fast answer :)
Just bind HybridSeed to the Yes-radiobutton. It will then either be true if the user has selected that or false if No-radiobutton has been selected (or if nothing has been selected). Binding to both buttons in this case is a bit redundant since the mechanism of radiobuttons takes care of it.
WPF:
<RadioButton Content="Yes" IsChecked="{Binding HybridSeed}" />
<RadioButton Content="No" />
<Label Content="{Binding HybridSeed}" ContentStringFormat="Value is: {0}" />
Logic:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
private bool hybridSeed;
public bool HybridSeed
{
get { return hybridSeed; }
set
{
hybridSeed = value;
OnPropertyChanged(nameof(HybridSeed));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm working on a WPF project and its MVVM. I got a problem about refreshing combobox binding value. So, here its the case, i have a combobox and button on my grid. I need to change datasource then refresh to see new values according to selected one step before.
Please tell me what is the best method to like next button? After that maybe need to like previous button.
public MyScriptForm(IMyScriptModel viewModel) {
this.Model=viewModel;
InitializeComponent();
Height=Double.NaN;
Width=Double.NaN;
}
public IMyScriptModel Model {
get {
return this.DataContext as IMyScriptModel;
}
set {
this.DataContext=value;
}
}
private void btnNext_Click(object sender,
RoutedEventArgs e) {
/// to what ?
Model.cbxAnswer.Clear();
Model.cbxAnswer.add("Step2Data");
}
Create{//its a huge project, this working on when this form created
myScript.Model.cbxAnswer.Add("1");
myScript.Model.cbxAnswer.Add("2");
myScript.Model.cbxAnswer.Add("3");
}
destroy{}
////////////////////////
//Onmy model
public List<string> cbxAnswer {
get {
return m_cbxAnswer;
}
set {
m_cbxAnswer=value;
OnPropertyChanged("cbxAnswer");
}
}
public List<string> m_cbxAnswer=new List<string>();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
if (PropertyChanged !=null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
XAML:
<Grid>
<ComboBox Name="cbxAnswer" HorizontalAlignment="Left" Margin="10,130,0,0" VerticalAlignment="Top" Width="130" Height="25" ItemsSource="{Binding Path=cbxAnswer}" />
<Button Name="btnNext" HorizontalAlignment="Left" Margin="215,130,0,0" VerticalAlignment="Top" Width="75" Content="İlerle" Click="btnNext_Click" />
</Grid>
You are just adding and removing from the collection (as opposed to creating a new one). This means that you need a collection that implements INotifyCollectionChanged.
A very convenient class that already does this is ObservableCollection<T>, which I would use instead of List<T> here, and anywhere else you need collection changes to propagate to the UI.
If you are truly doing a full refresh, you may consider just recreating the collection instead of doing add/removes. This could give you a net performance gain, as doing each operation individually must be handled by the UI, as opposed to all at once.
I've searched here a number of times and found a bunch of examples, but can't seem to get anything to work.
I've got a solution set up where a ViewModel refers to a MainViewModel class through a locator. The main view model class has:
public NotifyLog Log
{
get { return LogMgr.Instance.Log; }
}
In it. This allows me to specify:
<TextBox IsEnabled="True" Text="{Binding Log.Text, Mode=OneWay}" />
The NotifyLog is defined as:
public class NotifyLog : INotifyPropertyChanged
{
public NotifyLog()
{
_Log = new StringBuilder();
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
private StringBuilder _Log;
public void Append(String Value)
{
_Log.Append(Value);
OnPropertyChanged("Text");
}
public string Text
{
get { return _Log.ToString(); }
}
public override string ToString()
{
return this.Text;
}
}
For the initial start of the application, the text box is populated but, the OnPropertyChanged handler is never automatically populated by the binding so no changes are detected. I'm doing something wrong, I just don't know what...
Thanks for your time,
BlD
if you want to update the log when typing in the text box you need to change the binding mode to TwoWay. also the event is triggered when you exit from the text box, not on each char typed.
if you want to update the text box when the log is changed you need to add a setter to the Text property and raise the NotifyPropertyChanged event (in the setter).
also check the output of the program for some binding errors.
To the line:
<TextBox IsEnabled="True" Text="{Binding Log.Text, Mode=OneWay}" />
Try adding the "UpdatedSourceTrigger" like so:
<TextBox IsEnabled="True" Text="{Binding Log.Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />