Refresh all contexts on SaveChanges - c#

I have a WPF application where I have two windows. One is for listing objects (e.g. Books) and the other one is a form for editing one object (e.g. a Book). I create a new EF context for each of the windows.
Now I wonder, is there a way I can detect in the list window that a context has been edited after I call SaveChanges in the edit window?
What I do at the moment is to have a public Update method on my list window object that I call from the edit window after SaveChanges is called and in that function I call for the context refresh. However that is not scalable as I would need to keep track of all the windows that depend on Book information (e.g. if I add Shelves list I would need to make sure to update that one as well).
Any suggestions on how to solve this in more modular fashion?
P.S.
The funny part is that I seemed to have had an idea of how to improve this 2 years ago when I first wrote this code as I made a comment for myself: "TODO: solve this differently by having events in ArTresorEntities". But I don't remember what I meant.

Using MVMM Light you can send Message. Your List Window registers receiving message and latter notifies it about change. Take a look here.

You can do this by implementing INotifyPropertyChanged on your model class and then binding the value to your label/interface control.
Here's an example model class:
public class Data : INotifyPropertyChanged
{
int random;
public int Random
{
get { return random; }
set { random = value; NotifyPropertyChanged("Random"); }
}
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
That's all that's needed to implemented the INotifyPropertyChanged. Notice that you have to call NotifyPropertyChanged during the set method of the property, otherwise this won't work.
On the UI side, you would do something like this:
<Grid>
<Label Content="{Binding Random, UpdateSourceTrigger=PropertyChanged}" />
<Button Width="100" Height="40" Content="Randomize" Click="Button_Click" />
</Grid>
And then lastly, the code-behind for the xaml:
public Data data;
public MainWindow()
{
InitializeComponent();
data = new Data();
this.DataContext = data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
data.Random = new Random().Next(1000);
}
The DataContext being assigned is just an example, but it binds this particular class to this window's dataContext, allowing me to do the {Binding Random} and know which property I'm talking about and in which class. You can do this in a few ways and split it up throughout a view.
I also added a button that generates a random number and sets it in my Random property on my model. If you run this code, it updates the label each time you click the button.

Related

INotifyPropertyChanged doesnt fire when property changes [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I have a bool value that gets updated and the INotifyPropertyChanged event doesnt fire properly. It gets to the IF portion and steps over because it sees a null being passed. I did add using System.ComponentModel at the top of the class
The first portion:
public class ToolTipInortfyPropertyChange : INotifyPropertyChanged
{
private bool form15TooltipShow;
public bool Form15TooltipShow
{
get { return form15TooltipShow; }
set
{
form15TooltipShow = value;
OnPropertyChanged("Form15TooltipShow");
}
}
and then the second portion all in the same class
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
when I step in the code it gets to the "Set" portion then jumps to the OnPropertyChanged and skips the if so my view doesnt get updated with another event needing the bool
Tx
This the case with every event in c#, if no one subscribed to the event then it will be null. In this case it seems like no one subscribed to the PropertyChanged event.
PS: I recommend putting the [CallerMemberName] attribute in the OnPropertyChanged method so you don't have to pass the name along manually every time. Like so:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
Notice how I made the method protected instead of public, we don't wan't just anyone being able to raise the event
Edit: Here's the full OnPrertyChanged method:
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When I step through the code I can see that
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
does execute and the value is no longer null so there must be a "listener" I gather. But the view doesnt update. The checkbox is on one form and the control that will receive the event from the checkbox is on another form. Its almost like 2 objects are created and they cant talk to each other because they are 2 different enteties. Is that possible when creating a new object of lets say type person on form 1 and the same on form 2. They will be distinct wouldnt they?
I created a whole new project and made it as simple as possible. Textbox on form1 and textbox on form 2 and try to pass text between these 2 forms. The line
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
excecutes with no Null value but the view doesnt update.
Here is the XAML
<TextBox Text="{Binding Path=CheckBoxState ,Mode=TwoWay}"></TextBox>
and I have tried all kinds of different options like ASYNC etc. The result is always the same. I even removed the code behind from the forms and did the binding on the XAML
<Window.Resources>
<local:CheckBoxClass x:Key="CheckBoxFinder" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource CheckBoxFinder}}">
I believe this worked fine because intelensense automatically comes up after I type PATH=
then the only property listed pops up.
any advice would be appreciated.
tx
Ok just used the EXACT same code and simply added another textbox to form1. So form1 has 2 textboxes. I bound them exactly as previously only difference is the controls sending and receiving are on the same form. And guess what... It worked.
The problem arises when 2 forms need to pass data. Its like 2 objects are created and they cant communicate. I'm sure this is common knowledge to all the seasoned guys / gals. But how do I use one object between these forms so they will be synced? Wish I knew this before could have asked my question more concisely

Multiple views sharing same data with two-way data binding between multiple threads

UWP app ( mvvm architecture ) I have a MainView which has a collection in its ViewModel, used to bind to the GridView on MainView and each item has a TextBox with 2 way databinding with Description property of class Note.
Xaml of the TextBox of each gridviewitem.
<TextBlock Text="{x:Bind Description,Mode=TwoWay}"
Collection property used to bind to ItemSource of gridview.
public ObservableCollection<Note> Notes { get; }
and this is the class Note
public class Note : Observable
{
private string _description;
public string Description
{
get => _description;
set => Set(ref _description, value, nameof(Description));
}
}
the Observable class is for two way data binding help.
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Now everything uptil this point works perfectly, when I change the text in textbox, it changes the value of Description as well.
Second View
Now I have a feature where each GridViewItem has a button in it which opens the Note in new window. and this new window has nothing but only 1 TextBox, so now the secondary view and the GridViewItem which opened that view are using the same object of Note.
This TextBox in secondary view also has 2 way data binding with the Description of the Note.
The Problem
What I want is that whether the textbox in gridview or the textbox on the secondary view is edited, the value of description must remain synced between these 2 textboxes, that is why I tried to bind them 2 way with same object of Note hence the same Description object is bound to both of them.
Error here was expected to me which was Marshalling threading error, so whenever I try to change value of any textbox, it tried to update UI on other view ( which is another thread ) which is ofcourse not allowed.
I know about CoreDisptcher
I already know about the Dispatcher feature of UWP for safe cross thread communication, I already have it all setup and if I use it from a normal method I can easily use it for cross thread UI update and it totally works. But my issue is the following line :
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\
Exception occurs when it tried to invoke the PropertyChanged I tried to wrap following line in my Dispatcher :
OnPropertyChanged(propertyName);
but INotify interface does not allow me to have a Set<> method which returns a Task instead it needs to return just an object, this is the point where I am stuck and I dont know how to make use of Dispatcher in this scenario, please let me know if there is some better way to do this, it seems this way might not be so efficient.
Thanks.
The best solution in this case would be to have a separate set of INotifyPropertyChanged instances for each window and using some kind of messaging solution like EventHub in MvvmLight, which publishes message that the underlying model changed and all interested parties should update their instances.
Another option would be to create a base model class, which maintains a dictionary of INotifyPropertyChanged instances for each UI thread (so it would be a Dictionary<Dispatcher, YourModelClass>. Now the parent would subscribe to PropertyChanged event of each child instance and once it executes would propagate the event to other childs using the appropriate Dispatcher.
Also there is a very interesting utility class ViewSpecificBindableClass by Marian Dolinský on his GitHub which could potentially be a solution that would allow you to have "single" class in multiple views, aware of multiple dispatchers. I haven't tried it yet, but it seems promising.
So I finally had to take a totally different approach centralizing TextChanged events of MainView textbox and the one on the secondaryview.
I essentially passed the textbox on the mainpage through to the secondary page ( secondary view ) and then subscribed to its TextChanged event. I also subscribed to the TextChanged event of textbox on the secondary view, and then with help of reverse dispatchers I was able to sync the text between 2 windows without any problems.
Note : always make sure to unsubscribe to events when the secondary window closes to prevent memory leaks.
private async void PipBox_TextChanged(object sender, TextChangedEventArgs e)
{
string text = PipBox.Text;
await CoreApplication.MainView.Dispatcher.AwaitableRunAsync(() =>
{
if (parentBox.Text != text)
parentBox.Text = text;
});
}
private async void ParentBox_TextChanged(object sender, TextChangedEventArgs e)
{
string text = parentBox.Text;
// the awaitablerunasync extension method comes from "Windows Community Toolkit".
await _viewLifetimeControl.Dispatcher.AwaitableRunAsync(() =>
{
if (ViewModel.MyNote.Description != text)
ViewModel.MyNote.Description = text;
});
}
Notice that I still have 2 way data binding on both textboxes and it does not cause any exceptions because I am using 2 different instances of Note for both views.
<TextBox Text="{x:Bind ViewModel.MyNote.Description, Mode=TwoWay}"
x:Name="PipBox"/>
but because I have twoway data binding on both textboxes, that is how I can easily keep both instances of Note in sync as well on separate threads.
I will keep the github repo in case it can help anyone else : https://github.com/touseefbsb/MultiWindowBindingSync
P.S : A special thanks to Martin Zikmund who helped me a lot in figuring out this solution.

Using a Button_Click event to change textbox content using MVVM [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I don't even know if that's the right title for this but anyway. I'm switching from WinForms and trying to learn WPF and the MVVM methodology.
I have a custom class, Incident, used to store data about incidents that occur that my team must respond to. I am building a View to display the data in instances of that class to a user, as well as allow that user to manipulate it. There are several pieces of DateTime data that need to be displayed - Start, End, Notification_Received, Actions_Taken. I need to have a small button that will put DateTime.Now into each associated TextBox as well as change the underlying value of the currently loaded instance of the Incident class.
I'm trying to figure out how to do this. With WinForms, I would've just set TextBox.Text and Incident.Start (etc) to DateTime.Now all in the same Button_Click function, but my understanding of MVVM is that I'm not supposed to do this, instead I should bind the TextBox to the VM and update the value of the VM.
This is where I'm stuck. I'm pretty sure I'm good on how to do the binding, but not the part where I change the value of the VM from my Button_Click function. Please assist?
You're correct - the view-model should control the change, and the textbox should update through a binding.
In the MVVM pattern, code-behind is rarely used. Instead of a Button_Click method, you need a command binding:
<Button Command="{Binding SetAllDatesToNowCommand}"/>
The command will be executed when the button is pressed. SetAllDatesToNowCommand is a command handler - it should be an ICommand property on your view-model:
public ICommand SetAllDatesToNowCommand { get; private set; }
I generally tend to use RelayCommand from the MVVM Light Toolkit to create command handlers, because the syntax is clean and very simple. The command handler is initialized in your view-model's constructor. The handler method passed to RelayCommand is where you should set properties on the selected Incident object:
public YourViewModel()
{
this.SetAllDatesToNowCommand =
new RelayCommand(this.ExecuteSetAllDatesToNowCommand);
}
...
public void ExecuteSetAllDatesToNowCommand()
{
this.selectedIncident.Start = DateTime.Now;
// etc.
}
If the bindings on your textboxes are correctly set up, and the properties that are being set are firing appropriate PropertyChanged events, they should be updated when the properties are set in the command execution method.
However, I'd suggest that you should have a view-model for Incident, which implements the INotifyPropertyChanged interface. The command outlined above would be a property on that view-model. Setting, for example, the Start property on that view-model should set the property on the Incident object it is the view-model for (the "model" object), and should also raise a PropertyChanged event. Otherwise, your Incident class will have to implement INotifyPropertyChanged, and the line between model and view-model classes becomes less clear.
I assume you've bound the form to your ViewModel. Therefore you have a property in your ViewModel of Start. You want to bind a field to that
<TextBlock Text={Binding Start}/>
or
<TextBlock Text={Binding Incident.Start}/>
Depending on how you expose Incident. To update Start you have to do two things. Use a command on the button.
<Button Command="{Binding TestCommand}">Test</Button>
In your ViewModel you'd define the command.
private RelayCommand _testCommand;
public RelayCommand TestCommand
{
get
{
return _testCommand ?? (_testCommand = new RelayCommand(TestUpdate, CanRunTest));
}
set
{
if (_testCommand == value) return;
_testCommand = value;
}
}
public bool CanRunTest()
{
return some boolean test that defines if the command can run now;
}
private void TestUpdate()
{
Incident.Start = DateTime.Now;
}
RelayCommand is a helper method you can find in MVVMLight. Also see Josh Smith for info on RelayCommand.
Second, you will need to implement INotifyPropertyChanged on the Incident model or look up ObservableObject and make your Start property look like this.
public class Incident : ObservableObject
{
private ObservableCollection<WordLetter> _start;
public virtual ObservableCollection<WordLetter> Start
{
get { return _start; }
set
{
if (value == _start) return;
_start = value;
NotifyPropertyChanged();
}
}
}

Navigation Back & MVVM - How to refresh WP8 page databinding

I'm doing a WP8 App (C#/XAML).
In my view I specify a button, which contect is set by binding with a callback, for the start of an app, when the VM is not fully loaded.
MVVM looks like:
ViewModel
---------
+ Model
-----
+Property
And is created in App.xaml.cs like this:
public static MainViewModel ViewModel
{
get
{
if (viewModel == null)
{
viewModel = new MainViewModel();
}
return viewModel;
}
}
And set to the page as datacontext in contructor of the page:
DataContext = App.ViewModel;
And button:
<Button x:Name="btn" Content="{Binding Model.Property, FallBackValue='click to load'}" .../>
At the start, the btn does not have a value to put in it's content, because model is empty.
When the btn is clicked, it loads the model. It fills model with data and navigates to another page which shows that data.
And when i navigate back (through hardware back button) I'd like btn to use the value from binding instead of a fallback, because the value is already set. But It doesn't use it and still uses the one provided by FallbackValue argument of binding.
How to ensure, that the page "refreshes" an uses actual values provided by ViewModel?
Ahh, ok found solution to my problem myself.
THE PROBLEM
If you're using static Datacontext (if the Viewmodel class you use is created as static), then when you navigate back to the page, the databinding won't update (at least that is how it was in my case).
I use same datacontext (ViewModel containing multiple models and inside some collections and properties) for multiple pages. But when I navigated back to the page through hardware back button, the databinding was not updated.
The content of a button/textblock is stuck at the old value, even though you changed it to some new one.
Solution
Override the OnNavigatedTo Method, and set databinding there instead in contructor. This way you can be sure, that the databinding is always "fresh" and updated.
Inside the page class in code-behind (the .xaml.cs file sticked to your .xaml page) write this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e); //can be left out, base method is empty
DataContext = null; //setting datacontext empty at first
DataContext = App.ViewModel; //and setting it to the static ViewModel i created
}
This way, the DataContext is always first set to null, when I come to the page (so that the old values clean and there is nothing to bind from).
And shortly after that, i put the original DataContext back, so it has something to bind from again.
The step with null is necessary, because i need the datacontext property to change, otherwise if I just point again at the same obect that is already set as dataContext, nothing will happen.
I guess your ViewModel would be implementing INotifyPropertyChanged. To refresh the data binding you just need to raise property change event implemented in your model. In OnNavigatedTo event of your page check if Model is empty or not. If not raise property change
In your view model
public class ViewModel:INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
In your page
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
if (App.ViewModel != null)
App.ViewModel.NotifyPropertyChanged("Name of property");
}

What is the "proper" way in WPF MVVM to bind a frameworkElement event to a viewmodel handler?

So here is my dilemma, I want to handle view events on my view model, trouble is, in order to add an event handler, my view has to have a code behind file and therefore a class attribute has to be set. I'm 99% sure this is a bad idea, and to be perfectly honest, i'm not even sure how to do it (other than the obvious x:Class="" part) What is the proper way to do this in an MVVM application?
<ResourceDictionary>
<DataTemplate DataType="{x:Type vm:OutletViewModel}">
<Button Click="IHaveNoBinding">
</DataTemplate>
</ResourceDictionary>
Use commands:
<Button Command="{Binding ACommandOnYourViewModel}"/>
See this post of mine for a useful command implementation you can use in your view models.
Assuming you can't use commands, use attached command behaviors.
I use Attached Behaviors. Attached behaviors basically translate events into commands. Check out this link for an example:
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
Here is the code for a TextChangedBehavior.
public static class TextChangedBehavior
{
public static readonly DependencyProperty TextChangedCommandProperty =
DependencyProperty.RegisterAttached("TextChangedCommand",
typeof(ICommand),
typeof(TextChangedBehavior),
new PropertyMetadata(null, TextChangedCommandChanged));
public static ICommand GetTextChangedCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(TextChangedCommandProperty);
}
public static void SetTextChangedCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(TextChangedCommandProperty, value);
}
private static void TextChangedCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBoxBase textBox = obj as TextBoxBase;
if (textBox != null)
{
textBox.TextChanged += new TextChangedEventHandler(HandleTextChanged);
}
}
private static void HandleTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox != null)
{
ICommand command = GetTextChangedCommand(textBox);
command.Execute(textBox.Text);
}
}
}
XAML:
<TextBox behavior:TextChangedBehavior.TextChangedCommand="{Binding TextChangedCommand}" />
Generally I will not use the attached behavior pattern for simple things like this. As a consultant I find It complicates things for newer developers.
So how then do you handle control interaction when no commands are available. Get ready to pick your self up off of the floor :-) I will often times use the code behind for this. The event handler in the code behind handles the event, it gathers any data it needs from the event args and then forwards the request to the View Model. You do not lose much by doing this as most things that do not support ICommand cannot leverage the hide/show/enable/disable anyway.
There are some rules however. The code behind can only be used for control forwarding to the View Model. As long as you don't pass event arguments directly to the View Model I think it is fine to use events in this way. The fact of that matter is in large scale applications you cannot always get away from having code behinds. If you use them as they were intended i.e. page controls I see no harm in doing so.
Code behind isn't a bad thing at all. There are enough scenarios where you can't use WPF data binding (e.g. PasswordBox) and then you have to create a code behind file.
How you can use a PasswordBox without binding is shown in the ViewModel example of this project:
WPF Application Framework (WAF)
http://waf.codeplex.com

Categories