I'm pretty new to WPF, and now I stumbled on something for which I could not find the answer anywhere on the internet. I have the following problem:
Within the same solution, I have 2 projects. One is an application that represents a production process, called MaintenancePlanner. The other is a GUI called MaintenancePlannerGUI.
What I want to achieve is the following: upon pressing a button, the simulation of my production process starts (which takes place in MaintenancePlanner). Then, in the MaintenancePlannerGUI, I have for example a progressbar. The value of the progressbar should change according to the value of the property of an object within the MaintenancePlanner simulation.
Therefore, I need to bind this somehow. However, I don't understand how to do this. I make use of the MVVM structure. So my structure looks like follows:
MaintenancePlanner
AssemblyFab.cs
AssemblyLine.cs
ShellModel.cs (something like Program.cs, but specifically to be used for MaintenancePlannerGUI only)
MaintenancePlannerGUI
Views
ShellViewModel.cs
ViewModels
ShellView.xaml
Now, AssemblyLine for example contains a property Speed. Note that multiple instances of AssemblyLine are attached to AssemblyFab, in the form of a List<AssemblyLine> AssemblyLines.
In ShellView.xaml I have a progressbar:
<ProgressBar Width="10" Height="45" Margin="0,5,10,0" Orientation="Vertical" Minimum="0" Maximum="50" Value="{Binding ???}"/>
In ShellViewModel.cs I create an instance of the MaintenancePlanner simulation AssemblyFabSim by creating an instance of ShellModel.cs from MaintenancePlanner where the whole AssemblyFab and its constituents are created, like this:
AssemblyFabSim = new ShellModel();
Now, I tried something very crude like:
Value="{Binding AssemblyFabSim.AssemblyFab.AssemblyLines[0].Speed}
But that obviously didn't work. Another idea that came to my mind is to make use of the NotifyPropertyChanged Methods.
So in that case, I could create a property in ShellViewModel.cs named for example test and bind that to my progressbar. Then I could update test by getting a notification if the property changed in the ShellModel.cs. But then I also need to monitor the changes in AssemblyFab and AssemblyLine from within ShellModel.cs, so to propagate the change from AssemblyLine to AssemblyFab to ShellModel to ShellViewModel to the View. And I am a little bit confused about this approach.
private void ShellModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Speed")
{
test = AssemblyFabSim.AssemblyFab.AssemblyLines[0].MouldCleanInterval;
}
}
I was wondering whether this is indeed the way to go, and if so, how to do this exactly? Or are there perhaps other simpler ways? Thanks in advance!
Edit 1
My ShellViewModel.cs now contains the following, as ShellViewModel inherits the INotifyPropertyChanged class like this ShellViewModel : INotifyPropertyChanged
public ShellModel AssemblyFabSim { get; set; }
AssemblyFabSim.PropertyChanged += ShellModel_PropertyChanged;
private void ShellModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "TestSpeed")
{
test = AssemblyFabSim.AssemblyFab.AssemblyLines[0].Speed;
}
}
private double _test;
public double test
{
get { return _test; }
set
{
_test = value;
NotifyOfPropertyChange();
}
}
And I now bind my progressbar value like Value="{Binding test}. Then ShellModel.cs also will inherit INotifyPropertyChanged and I add:
public void UpdateSpeed()
{
try
{
TestSpeed = AssemblyFab.AssemblyLines[0].Speed;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
NotifyOfPropertyChange(nameof(TestSpeed));
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyOfPropertyChange([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And the UpdateSpeed() method is called from within the Assemblyline.
New problem: The value of the processbar gets updated, but only after the simulation is finished I see the result. That is, the GUI just freezes until the simulation stops and it then shows the last value of Speed.
If your shellViewModel has a property like
public ShellModel AssemblyFabSim {get;}
you should be able to bind to it, and if all the other properties in the path is correct they should also work. But as far as I know, bindings does not support indexers, so I do not think AssemblyLines[0] will work.
Wpf does not care about what projects classes are defined in, only that it has a object to bind to, and the properties are correctly named. Usually everything should also implement INotifyPropertyChanged to work well.
But note that deeply nested paths is probably not a good idea, you should try to separate the UI and business logic, so you should try to avoid binding to anything other than viewModel classes designed for it.
Notably, if you have a progress bar you should bind to a Progress<T> object that is handed to the method that needs to report progress. You should avoid using a progress-property on the object performing the work, after all, what would happen if the method was called concurrently from multiple threads?
You need to ensure that you are calling the UpdateSpeed() on a background thread since the UI thread cannot both update the progress bar and execute your code simultaneously.
Related
I'm currently trying to figure out how to automate UI testing for my WPF application and I have troubles getting it to work.
The XAML of MyControl (which extends UserControl) contains the following CheckBox:
<CheckBox Name="IsFooCheckBox"
IsChecked="{Binding Path=IsFoo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
The binding points to a custom data context that implements INotifyPropertyChanged and that contains the following property:
private bool _isFoo;
public bool IsFoo
{
get { return _isFoo; }
set
{
_isFoo = value;
OnPropertyChanged("IsFoo");
}
}
The binding is working in production (in the debugger is can see that _isFoo is updated whenever I toggle the checkbox).
I'd like to have a test now that toggles the checkbox and checks that the data context is updated (or to check logic that is implemented in the code-behind). The WPF UI Automation framework seems to be exactly what I am looking for, so I wrote the following NUnit test:
var myContext = ...
var sut = new MyControl
{
DataContext = myContext
};
var peer = new CheckBoxAutomationPeer(sut.IsFooCheckBox);
var pattern = peer.GetPattern(PatternInterface.Toggle) as IToggleProvider;
pattern.Toggle();
Assert.That(sut.IsProvidingProfileCheckBox.IsChecked.Value); // works
Assert.That(myContext.IsFoo); // fails
While the first Assert passes, the second one fails. I do not understand why this happens... it seems that the binding in the XAML file is ignored or that the update is not triggered. Does anybody have a suggestion how to fix my test? Is this even possible?
Issue originates here
public bool IsFoo
{
get { return _IsFoo; }
set
{
_isFoo = value;
OnPropertyChanged("IsFoo");
}
}
Once you have invoked
pattern.Toggle();
you implicitly invoke setter of IsFoo which raises PropertyChanged event and in turn forces to refresh UI elements which have binding associated with IsFoo - to cut a long story short, getter is invoked and instead of _isFoo it returns _IsFoo. You mistook variable.
Try to avoid calling OnPropertyChanged method with explicit property name. Instead of this use CallerMemberName attribute which retrieves property name.
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
Then you only need below invocation.
OnPropertyChanged();
I had similar problem. I was populating textbox with following code:
ValuePattern valuePattern = promptBox.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
valuePattern.SetValue(value);
And depending on the value in text box, another button state was supposed to change from Disabled to Enabled.
I've noticed that after automation code above executed, clicking on the window by hand triggered binding to evaluate.
So I just added
System.Windows.Forms.SendKeys.SendWait("{TAB}");
after SetValue and it started to work just fine.
It took me some time to figure this out, but in the end, it is kind of obvious and described in many places all over the internet. I guess everything is easy as soon as you know what to look for...
The problem of coded UI tests is that the bindings are not resolved automatically. The resolving has to be initiated by calling Window.ShowWindow.
I extended my test and added the following snippet:
Window window = new Window
{
Content = sut // the control to test
};
window.Show();
Adding this call instantaneously fixed the strange test behavior that I described in my post.
The direct follow-up problem is that this call requires an active UI thread. Because of this, it might be tricky to get the test to run on a build server in a continuous integration environment. However, this depends on the environment (esp. the build server) and is a different question.
I have a MvxViewController and in the ViewDidLoad i bind the button click to the viewmodel. When the button is clicked I open another view in which I will need to return a string back to my first view
public override void ViewDidLoad ()
{
var set = this.CreateBindingSet<MyView1, MyView1ViewModel>();
set.Bind(myButton).To(vm => vm.MyButtonCommand);
set.Apply();
}
public ICommand MyButtonCommand
{
get
{
_myButtonCommand = _myButtonCommand ?? new MvxCommand(MyButtonCommandClick);
return _myButtonCommand;
}
}
private void MyButtonCommandClick()
{
ShowViewModel<ViewModelNumber2>();
}
After some logic is ran in my second view I want to return the string
private void SomeMethodInViewModelNumber2()
{
//Raise event that will get pickup up in MyView
//Or somehow get "SomeString"
if (OnMyResult != null)
OnMyResult ("SomeString");
}
The problem is that I don't want to send the string back using the messenger. I have my reasons but basically because ViewModelNumber2 can be opened from many different places and works slightly different and managing the different messages that would need to be sent back and where to subscribe to these messages would be a mess
Is there any way that I can do something like the below?
public override void ViewDidLoad ()
{
var set = this.CreateBindingSet<MyView1, MyView1ViewModel>();
set.Bind(myButton).To(vm => vm.MyButtonCommand).OnMyResult((myString) => {Process(myString)});
set.Apply();
}
Or perhaps when I create ViewModelNumber2 I should pass a callBack into the constructor and use that to send the string back from ViewModelNumber2 to MyView1ViewModel
ShowViewModel<ViewModelNumber2>(OnMyResult);
What is the best way to do this?
In short: I don't know what "the best way to do this" is.
The area of ChildViewModel-ParentViewModel messages is complicated - especially because on platforms like Android using Activities and WindowsPhone using Pages you have no guarantee that the ParentViewModel will be in memory when the Child is shown. (Note: this isn't a problem on iOS as its "app suspension" model is simpler)
When I do need one ViewModel returning data to another, then:
Often I try to implement the data collection views as "popup dialogs" rather than as "whole pages" - this makes the parent-child ViewModel relationship more correct - and ensures the parent ViewModel will be in memory when the child closes.
Often I recommend people use a Messenger-based technique like Greg describes in: http://www.gregshackles.com/2012/11/returning-results-from-view-models-in-mvvmcross/
often I've done this messaging via background services rather than via ViewModel-ViewModel messaging (a bit like the way screens are updated in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/tree/master/N-17-CollectABull-Part6)
Another solution I've used is to:
implement a IDropBoxService singleton - with an API like void Deposit(key, value) and bool TryCollect(key, out value)
allow the closing "child" ViewModels to leave "values" when they close
implement IVisible functionality in my "parent" ViewModel - like in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-42-Lifecycles/Lifecycle.Core/ViewModels/FirstViewModel.cs#L10
use the IVisible method to check for messages
To implement anything perfectly, you really should add serialisation code to make sure this all works during "tombstoning" on all platforms... but often this is overkill - for a simple data collection dialog users often don't need "perfect" tombstoning support.
How should I be opening new windows? I'm currently doing the following.
EventArgs:
public class GenericViewRequestedEventArgs : EventArgs
{
public GenericViewModel ViewModel { get; private set; }
public GenericViewRequestedEventArgs(GenericViewModel viewModel)
{
ViewModel = viewModel;
}
}
ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private RelayCommand _viewSpecificCommand;
public ICommand ViewSpecificCommand
{
get
{
if (_viewSpecificCommand == null)
_viewSpecificCommand = new RelayCommand(x => viewSpecific());
return _viewSpecificCommand;
}
}
public EventHandler<GenericViewRequestedEventArgs> GenericViewRequested;
private void RaiseGenericViewRequested(GenericViewModel viewModel)
{
var handler = GenericViewRequested;
if (handler != null)
handler(this, new GenericViewRequestedEventArgs(viewModel));
}
private void viewSpecific()
{
RaiseGenericViewRequested(_specificViewModel);
}
}
View:
public partial class MainWindow : Window
{
private void OnGenericViewRequested(object sender, GenericViewRequestedEventArgs e)
{
GenericWindow window = new GenericWindow(e.ViewModel);
window.ShowDialog();
}
}
This does work, but it seems like a lot of code and I end up with code behind in my view any ways.
What's the logic behind sending the command to the viewmodel at all?
Is it just to optionally use the predicate(if so why not bind to Enabled) and possibly avoid exposing additional viewmodels as properties?
Should I be attaching simple event handlers in the XAML(e.g. Click="btnViewSpecific_Click")?
It depends on how "strict" you want to follow the MVVM pattern. This is just one of the basic pitfalls of MVVM and you can solve it depending on your preferences. One way is to simply use the code-behind, but then how will you handle application-wide commands, keyboard shortcuts, etc? It is a bit too short-sighted IMHO.
I think you should at least consider using existing frameworks that have solved these issues for you years ago and will provide you with a solid base for your application.
For example, Catel has a IUIVisualizerService that can show windows based on a view model. The major advantage is that you can push data into the view model and respond to the result as a service. Another nice advantage is that you can mock the IUIVisualizerService so you can test the reacting code on different results provided by the dialog.
** Disclaimer **
I am the developer of Catel, so I have explained the Catel way here. If anyone else wants to comment on other frameworks, feel free to comment or create a new answer.
Yes, there are a lot of additional codes for MVVM. Building a command that independent of Views is usually for unit testing, such that the command and ViewModel can be unit tested without involving UI components.
However, if the "command" is just opening a window, it is not worth to create a command, and unit test the command to see if the GenericViewRequested is really fired(you can even check if the correct _specificViewModel is returned). The codes are far more complicated and just little value is added. Just open the window in View's button click event handler and it is fine.
If you want to see good example, see how this works in the ViewModel (EmailClient) sample application of the WPF Application Framework (WAF).
I'm making a small demo application for MVVM with caliburn.
Now I want to show a MessageBox, but the MVVM way.
For dialogs I created an event, that is handled in the ShellView (the root view)
and just calls WindowManager.ShowDialog with a Dialogs ViewModel type.
Seems to stick to MVVM for me.
But what is the way to show a messagebox and get its result (Okay or cancel)?
I already saw this question, but it contains no answer either.
Mr Eisenberg hisself answers with
"Caliburn has services built-in for calling custom message boxes."
Can anyone tell what he means with that? I don't see it in the samples.
As you mentioned, you just prepare the view model (e.g. ConfirmationBoxViewModel) and an appropriate view. You'll have to create two actions (after inheriting the view model from Screen, which is necessary to use TryClose. You can always implement IScreen instead, but that would be more work):
public void OK()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
and then in your other view model:
var box = new ConfirmationBoxViewModel()
var result = WindowManager.ShowDialog(box);
if(result == true)
{
// OK was clicked
}
Notice that after the dialog closes, you can access the view model properties if you need to pull additional data from the dialog (e.g. Selected item, display name etc).
In the article A Billy Hollis Hybrid Shell (written by the framework coordinator) the author showed a nice way to handle both dialog and message boxes, but he used dependency injection (you can go without DI of course but it makes things simpler). The main idea is that you can let your main window, the one used as the application shell implement an interface that looks something like this:
public interface IDialogManager
{
void ShowDialog(IScreen dialogModel);
void ShowMessageBox(string message, string title = null, MessageBoxOptions options = MessageBoxOptions.Ok, Action<IMessageBox> callback = null);
}
and then he registers this interface with the IoC container, I guess you can use your imagination from there on and if you don't have time then you can look at the source code that accompanies the article.
When the root/main/shell view-model implements a kind of DialogService interface, every other view-model needing to show dialogs will end up with a dependency on the root view-model. Sometimes this might not be desiderable, e.g. if it could cause a dependency loop:
DialogService (aka RootViewModel) -> SomeViewModel -> RootViewModel.
A more involved approach to break this dependency chain (and actually invert it) is the following:
Implement a behavior that detects Window.OnSourceInitialized event and attach it to main view Window component. That is the event fired when the window handle is available. Upon event, behavior will notify some handler passed in via attached property:
<my:WindowSourceBehavior InitListener="{Binding WindowListener}" />
public class WindowSourceBehavior : Behavior<Window>
{
// ...
// boilerplate code for IWindowListener InitListener dependency property
// ...
attachedWindow.SourceInitialized += (sender, evt) =>
{
// ...
InitListener.SourceInitialized(sender as Window);
}
}
DialogService exposes a handler - or interface - as requested by behavior:
public class DialogService : IWindowListener
{
// ...
public void SourceInitialized(Window rootWindow) { /* ... */ }
}
In root view-model, (indirectly) get the DialogService injected as a dependency. During construction, sets view-model bound property, WindowListener, to the DialogService handler/interface:
public MainViewModel(IWindowListener dialogServiceInDisguise)
{
WindowListener = dialogServiceInDisguise;
}
public IWindowListener WindowListener { get; private set; }
Doing so, the DialogService is able to get a hold of root Window, and whichever view-model needs to show a dialog does not create a(n indirect) dependency on main view-model.
I am looking for a sample that demonstrates in the lightest way possible the following:
A Model that invokes a SOAP based web service; regularly polling to get the latest value (assume the SOAP service returns a boolean). The model should also support invoking a SOAP method that changes the boolean on the server.
A ViewModel that enables the underlying boolean to be bound to controls in the View (e.g. to a checkbox).
A View with the above checkbox control bound to the underlying boolean. Depending on the poll interval the checkbox will update as the server's state changes. If the checkbox is clicked the event will be dispatched to the model causing the server to be updated.
Optimally this sample will work on Windows Phone 7, but in a pinch I'd be happy with something that supported SL3 (no use of SL4 command routing allowed).
I am struggling with trying to understand how to make MVVM-Light work for me and I suspect that an expert could code a sample up like this very quickly... I also suspect this is a fairly common pattern for a lot of apps.
Mick N's pointer helped, but what really got me over the hump was this post by Jeremy Likness:
http://csharperimage.jeremylikness.com/2010/04/model-view-viewmodel-mvvm-explained.html
Here's the sample for the benefit of others (assuming I'm not doing anything really stupid):
First, I started using the Mvvm-Light Windows Phone 7 project.
I added a checkbox to my MainPage.xaml:
<CheckBox Content="Switch 1"
IsChecked="{Binding Switch1.PowerState, Mode=TwoWay}"
Height="72" HorizontalAlignment="Left" Margin="24,233,0,0"
Name="checkBox1" VerticalAlignment="Top" Width="428" />
Notice the IsChecked is bound to Switch1.PowerState using the TwoWay mode so that the property flows both ways.
A key learning for me is how to enable communication from my timer callback (TimerCB) which will be running on a new thread to the Silverlight UI thread. I used the Mvvm-Light DispatcherHelper.CheckBeginInvokeOnUI helper which waits on the UI thread.
I then had to decide whether to implement INotifyPropertyChanged myself in my model, or use Mvvm-Light's ViewModelBase implementation. I actually tried it both ways and had it working but decided I liked using ViewModelBase better because it supports "broadcast" and I think in my actual project that will be handy because I will have multiple ViewModels. It seems a bit uncouth to be basing a "Model" on ViewModelBase class, but I don't think there's any harm in doing so. (???).
My model .cs is below.
public class OnOffSwitchClass : ViewModelBase // ignore that it's derived from ViewModelBase!
{
private const Int32 TIMER_INTERVAL = 5000; // 5 seconds
private Timer _timer;
// Upon creation create a timer that changes the value every 5 seconds
public OnOffSwitchClass()
{
_timer = new System.Threading.Timer(TimerCB, this, TIMER_INTERVAL, TIMER_INTERVAL);
}
private static void TimerCB(object state)
{
// Alternate between on and off
((OnOffSwitchClass)state).PowerState = !((OnOffSwitchClass)state).PowerState;
}
public const string PowerStatePropertyName = "PowerState";
private bool _myProperty = false;
public bool PowerState
{
get
{
return _myProperty;
}
set
{
if (_myProperty == value)
{
return;
}
var oldValue = _myProperty;
_myProperty = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() =>
RaisePropertyChanged(PowerStatePropertyName, oldValue, value, true));
}
}
}
The MainViewModel.cs was modified to include the following
private OnOffSwitchClass _Switch1 = new OnOffSwitchClass();
public OnOffSwitchClass Switch1
{
get
{
return _Switch1;
}
}
And I added a call to DispatcherHelper.Initialize(); in my App() constructor.
Does this look right?