I have used Relay Commands quite a bit in WPF projects but I am currently working on Windows Workflows and I am making a custom activity with its own designer. I want a button on my design to add new fields if pressed. I have spent days trawling the internet but it seems windows workflow has defeated me. This is what I have and it does not work, if anyone knows why please help.
[Designer(typeof(MyCustomActivityDesigner))]
public sealed class MyCustomActivity : AsyncCodeActivity
{
public MyCustomActivity()
{
AddSpecificParameter = new DelegateCommand(AddParameter);
}
public DelegateCommand AddSpecificParameter { get; set; }
public void AddParameter()
{
//add value to an obervable collection
}
}
Relay command implementation:
public class DelegateCommand :ICommand
{
//Parameterless constructor needed as windows workflow serialises it
public DelegateCommand()
{ }
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
And the xaml is simply a button with a command binding but due to windows workflow weird stuff it goes through ModelItem (at least for the other bindings I have)
<Button Command="{Binding Path=ModelItem.AddSpecificParameter}"/>
So the key differences from code that I have in WPF apps that works is the parameter-less constructor but this shouldn't effect it? Tested this, parameter-less constructor works fine in WPF apps,so it has to be due to the ModelItem not being able to handle commands?
Also the ModelItem Binding path works for all the other bindable properties on the viewmodel (for example strings on labels)
Note: The activities are tested/displayed in a rehosted designer.
Related
I've got a behavior in my .net Core 3.1 WPF Application, which calls a command inside the ViewModel, after the view is displayed.
public class LoadedBehavior
{
public static DependencyProperty LoadedCommandProperty
= DependencyProperty.RegisterAttached(
"LoadedCommand",
typeof(ICommand),
typeof(LoadedBehavior),
new PropertyMetadata(null, OnLoadedCommandChanged));
private static void OnLoadedCommandChanged
(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
if (depObj is FrameworkElement frameworkElement && e.NewValue is ICommand)
{
frameworkElement.Loaded
+= (o, args) => { (e.NewValue as ICommand)?.Execute(null); };
}
}
public static ICommand GetLoadedCommand(DependencyObject depObj)
{
return (ICommand)depObj.GetValue(LoadedCommandProperty);
}
public static void SetLoadedCommand(
DependencyObject depObj,
ICommand value)
{
depObj.SetValue(LoadedCommandProperty, value);
}
}
This Behavior is attached inside the View:
behaviors:LoadedBehavior.LoadedCommand="{Binding LoadedCommand}"
I am working with Prisms RegionManager to inject my Views into specific areas inside the views. When I now try to inject a new view, the loaded command from the old view is called again. This seems like it comes from the bevavior.
For a better understanding, here is also the code which gets called to show a new view inside the specific region
public class NavigationService
{
private readonly IServiceLocator _serviceLocator;
private readonly IRegionManager _regionManager;
public NavigationService(IServiceLocator serviceLocator, IRegionManager regionManager)
{
_serviceLocator = serviceLocator;
_regionManager = regionManager;
}
public void Navigate(string regionName, object view)
{
RemoveAllViews(regionName);
_regionManager.AddToRegion(regionName, view);
}
public void Navigate<T>(string regionName) where T : FrameworkElement
{
var view = _serviceLocator.GetInstance<T>();
Navigate(regionName, view);
}
public void RemoveAllViews(string regionName)
{
_regionManager.Regions[regionName].RemoveAll();
}
}
Can anyone tell me, what I do wrong here? Or is this behavior not the way to go?
Edit
Right after posting this, I found the problem: The Loaded Command gets called multiple times. This seems to be caused by when the content of this view changes. So everytime I add a new view, the parent view calls it's loaded event. Is there a way to run the command only once the view is displayed?
The Loaded event is quite inreliable for triggering action with the intention of one time when the control is loaded. From the reference of the Loaded event for FrameworkElement.
Loaded and Unloaded might both be raised on controls as a result of user-initiated system theme changes. A theme change causes an invalidation of the control template and the contained visual tree, which in turn causes the entire control to unload and reload. Therefore Loaded cannot be assumed to occur only when a page is first loaded through navigation to the page.
In Prism you can act on navigation by creating a custom region behavior. In your example, you want to execute a command on a view model, once the view is added to a region. Create an interface that all your target view models implement with a command that should be executed when the view is displayed first.
public interface IInitializableViewModel
{
ICommand Initialize { get; }
}
Create a region behavior that watches the Views collection of a region and executes a command once, when a view is added to the region. It will check the data context of each view, if it implements the interface, the command is not null and command can execute.
public class InitializableDataContextRegionBehavior : RegionBehavior
{
public const string BehaviorKey = nameof(InitializableDataContextRegionBehavior);
protected override void OnAttach()
{
Region.Views.CollectionChanged += OnViewsCollectionChanged;
}
private void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var frameworkElement in e.NewItems.OfType<FrameworkElement>())
{
if (frameworkElement.DataContext is IInitializableViewModel initializableViewModel &&
initializableViewModel.Initialize != null &&
initializableViewModel.Initialize.CanExecute(null))
{
initializableViewModel.Initialize.Execute(null);
}
}
}
}
}
Add the custom region behavior in your Prism application to the region behaviors collection.
protected override void ConfigureDefaultRegionBehaviors(IRegionBehaviorFactory regionBehaviors)
{
base.ConfigureDefaultRegionBehaviors(regionBehaviors);
regionBehaviors.AddIfMissing(InitializableDataContextRegionBehavior.BehaviorKey, typeof(InitializableDataContextRegionBehavior));
}
The command on each view model will execute exactly once, when the corresponding view is added to any region. Using an interface here was easier for demonstration purposes, but you can also create an attached property for your command that you attach to your view and bind to the view model.
I'm currently working on a login screen and I wish for an ImageView to become visible upon "login" button click and rotate with an animation. I know how to animate the object, but since the animation is a unique android feature I have to initiate it from LoginView.cs in the .Droid project with e.g. a function and not simply from LoginViewModel in the .Core project.
I know how to treat MvxCommands in LoginViewModel.cs in .Core (and other MvxBind-ings), but I have no clue how to make the LoginView.cs in .Droid to detect this click event and play the animation from there. I other words, how do I make LoginView.cs in .Droid detect any changes happening in LoginViewModel.cs in .Core?? I have been Googlin' for several hours without any luck. Is this even possible? A solution would be god-sent.
Thanks in advance
The simplest way to handle ViewModel -> View communication is with a Func/Action delegate. Setup a property in your VM that takes a delegate, then assign the delegate in the view. You can then call the delegate from the VM.
Something like:
public class ViewModel {
public Action ClickDelegate { get; set; }
public ICommand ClickCommand {
get { return new MvxCommand(() => {
// call the action method to start animation
ClickDelegate?.Invoke();
};
}
}
}
public class MyView {
protected override OnCreate() {
// register delegate with VM
ViewModel.ClickDelegate = () => { StartAnimation(); };
}
}
I am not quite sure, where my problem/mistake is.
I am using WPF in combination with the MVVM pattern and my problem is at the login.
My first attempt worked fine. I had several windows, each with their own ViewModel.
In the Login ViewModel I had following code running:
PanelMainMessage = "Verbindung zum Server wird aufgebaut";
PanelLoading = true;
_isValid = _isSupportUser = false;
string server = Environment.GetEnvironmentVariable("CidServer");
string domain = Environment.GetEnvironmentVariable("SMARTDomain");
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, server + "." + domain))
{
// validate the credentials
PanelMainMessage = "username und passwort werden überprüft";
_isValid = pc.ValidateCredentials(Username, _view.PasswortBox.Password);
PanelMainMessage = "gruppe wird überprüft";
_isSupportUser = isSupport(Username, pc);
}
}
catch (Exception ex)
{
//errormanagement -> later
}
if (_isValid)
{
PanelLoading = false;
if (_isSupportUser)
_mainwindowviewmodel.switchToQuestionView(true);
else
_mainwindowviewmodel.switchToQuestionView(false);
}
else
PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
That part connects to an Active Directory and first checks if the login was succesfull and then, if the user has a certain ad group (in method isSupport)
I have a display in the view, which is like a progress bar. It is active when PanelLoading equals true.
Until now everything worked.
Then I created a main window with a contentcontrol in it and changed my views to user controls, so I could swap them. (The intention was, not to open/create a new window for every view).
When I execute the code now, my GUI blocks, until said part is executed. I have tried several ways...
Moving the code snippet into an additional method and starting it as an own thread:
Thread t1 = new Thread(() => loginThread());
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
When I do it this way, I get an error that a ressource is owned by an another thread and thus cannot be accessed. (the calling thread cannot access this object because a different thread owns it)
Then, instead of an additional thread, trying to invoke the login part; login containing the previous code snippet
Application.Current.Dispatcher.Invoke((Action)(() =>
{
login();
}));
That does not work. At least not how I implemented it.
After that, I tried to run only the main part of the login snippet in a thread and after that finished, raising an previously registered event, which would handle the change of the content control. That is the part, where I get the error with the thread accessing a ressource owned by another thread, so I thought, I could work around that.
void HandleThreadDone(object sender, EventArgs e)
{
if (_isValid)
{
PanelLoading = false;
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
}
else
PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
}
And in the login method I would call ThreadDone(this, EventArgs.Empty); after it finished. Well, I got the same error regarding the ressource owned by an another thread.
And now I am here, seeking for help...
I know that my code isn't the prettiest and I broke at least two times the idea behind the mvvm pattern. Also I have little understanding of the Invoke method, but I tried my best and searched for a while (2-3 hours) on stackoverflow and other sites, without succeeding.
To specify where the error with thread occurs:
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
which leads to the following method
public void switchToQuestionView(bool supportUser)
{
_view.ContentHolder.Content = new SwitchPanel(supportUser);
}
This is also one occasion, where I am not using Data Binding. I change the content of my contentcontrol:
<ContentControl Name="ContentHolder"/>
How would I implement this with Data Binding. Should the property have the type ContentControl? I couldn't really find an answer to this. And by changing this to DataBinding, would the error with the thread ownage be solved?
The project structure is as following:
Main View is entry point, in the constructor the data context is set to the mainviewmodel, which is created at that time. the main view has a contentcontrol, where I swap between my usercontrols, in this case my views.
from my mainviewmodel I set the content of the contentcontrol in the beginning at the usercontrol login, which creates a viewmodel in its contructors and sets it as datacontext.
The code snippets are from my loginviewmodel. Hope this helps.
I thought I found a workaround, but it still does not work. I forgot, how the timer works in the background, so it can be solved that way either.
The problem is that WPF, or XAML framawork in general, doesn't allow to modify visual elements on the main thread, from other threads. For solving this you should to distinguish which is the part of your code that update the view from the second thread. In your case I can see that:
_view.ContentHolder.Content = new SwitchPanel(supportUser);
changes the view.
For solving this you could try this answer. In which I use the synchronization context to the communication between threads.
Another way to solve it, (and it maybe is a wrong usage of the dispatcher) is using the dispatcher for "send" the actions that modify the view to the main thread. Some thing like this:
var dispatcher = Application.Current.Dispatcher;
//also could be a background worker
Thread t1 = new Thread(() =>
{
dispatcher .Invoke((Action)(() =>
{
login(); //or any action that update the view
}));
//loginThread();
});
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
Hope this helps...
One common approach is to implement an AsyncRelayCommand (in some tutorials also named AsyncDelegateCommand and bind it to the WPF view.
Here's an example implementation I used for a demo project to get familiar with WPF, MVVM and DataBinding.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
public class AsyncRelayCommand : ICommand {
protected readonly Func<Task> _asyncExecute;
protected readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged {
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncRelayCommand(Func<Task> execute)
: this(execute, null) {
}
public AsyncRelayCommand(Func<Task> asyncExecute, Func<bool> canExecute) {
_asyncExecute = asyncExecute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) {
if(_canExecute == null) {
return true;
}
return _canExecute();
}
public async void Execute(object parameter) {
await ExecuteAsync(parameter);
}
protected virtual async Task ExecuteAsync(object parameter) {
await _asyncExecute();
}
}
Here's the LoginViewModel.
// ViewBaseModel is a basic implementation of ViewModel and INotifyPropertyChanged interface
// and which implements OnPropertyChanged method to notify the UI that a property changed
public class LoginViewModel : ViewModelBase<LoginViewModel> {
private IAuthService authService;
public LoginViewModel(IAuthService authService) {
// Inject authService or your Context, whatever you use with the IoC
// framework of your choice, i.e. Unity
this.authService = authService
}
private AsyncRelayCommand loginCommand;
public ICommand LoginCommand {
get {
return loginCommand ?? (loginCommand = new AsyncCommand(Login));
}
}
private string username;
public string Username {
get { return this.username; }
set {
if(username != value) {
username = value;
OnPropertyChanged("Username");
}
}
}
private string password;
public string Password {
get { return this.password; }
set {
if(password != value) {
password = value;
OnPropertyChanged("Password");
}
}
}
private async Task Search() {
return await Task.Run( () => {
// validate the credentials
PanelMainMessage = "username und passwort werden überprüft";
// for ViewModel properties you don't have to invoke/dispatch anything
// Only if you interact with i.e. Observable Collections, you have to
// run them on the main thread
_isValid = pc.ValidateCredentials(this.Username, this.Password);
PanelMainMessage = "gruppe wird überprüft";
_isSupportUser = isSupport(Username, pc);
}
} );
}
}
Now you bind Username and Password properties as Two-Way bindings to your text fields and Bind your LoginCommand command to your login button.
Last but not least, a very basic implementation of the ViewModelBase.
public abstract class ViewModelBase<T> : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Some remarks at the end:
There are several issues with your code above, as you already mentioned. You reference the View from ViewModel. This pretty much breaks the whole thing and if you begin to reference views from ViewModel, you can skip MVVM wholly and use WPF's CodeBehind.
Also you should avoid referencing other ViewModels form your ViewModel, as this tightly couples them and makes unit-tests pretty hard.
To navigate between Views/ViewModels, one usually implement a NavigationService. You define the Interface of the NavigationService (i.e. INavigationService) in your model. But the implementation of the NavigationService happens in the Presentation Layer (i.e. the place/Project where your Views reside), since this is the only place where you can implement a NavigationService.
A navigation service is very specific to an application/platform and hence needs to be implemented for each platform a new (Desktop, WinRT, Silverlight). Same goes for the DialogService which displays Dialog messages/popups.
I'm building a small app that is based on MVVM and uses Autofac as a dependency injector.
I've created the VMBase which all the ViewModels depends from and I'm also using VMLocator to inject all the dependencies (using Autofac as stated before).
public class VMLocator
{
IContainer container;
public VMLocator()
{
var builder = new ContainerBuilder();
builder.RegisterType<VMRetrieveInfo>();
container = builder.Build();
}
public VMRetrieveInfo RetrieveInfoViewModel
{
get { return container.Resolve<VMRetrieveInfo>(); }
}
}
Got a DelegateCommand class that handles Raises of CanExecute:
public void RaiseCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
this.CanExecuteChanged(null, new EventArgs());
}
My problem comes when I try to put a button using DelegateCommand class that changes its state using a CanExecute method which returns a variable that is changed when I press another button.
Changing the value of the variable doesn't seem to launch any event that changes the CanExecute result (the CanExecute method is not even called) and I'm pretty lost.
public ICommand NavigateCommand
{
get { return navigateCommand; }
}
public void NavigateCommandExecute()
{
}
public bool NavigateCommandCanExecute()
{
return canCallWS;
}
Running a similar app without dependency injection (Autofac) has worked for me before.
Any help?
Thanks
In the DelegateCommand class you have a CanExecuteChanged event, the same way you did a RaisePropertyChanged method to launch the PropertyChanged in your ViewModel base, you need to do a RaiseCanExecuteChanged method in your DelegateCommand class to launch the CanExecuteChanged event when you change the condition...
In WPF we have a CommandManager who makes this job for us without need to launch the event. In Windows Phone we need to launch the event. This is not an autofac or DI issue, is the way command can execute works in Silverlight/Windows Phone.
Hope this helps!
In the application I'm building, the user may perform something in one view (backed by a view model) that should trigger an action in one or more other view models. Each of these other vms needs the ability to veto (a.k.a. cancel) the action performed in the first v/vm pair.
Example:
User clicks on an account in a DataGrid control displayed by the accounts list view. DataGrid event handler traps the click and tells vm. Vm notifies other vms of proposed change.
Since user has made unsaved edits to a record in another other view, other vm tells first vm that the proposed selected account change is rejected.
When accounts list vm receives this rejection, it tells DataGrid to keep the selected account set as it was. If no rejection had been received, accounts list vm would have allowed DataGrid selected item change to occur.
A similar scenario would be when the user initiates application shutdown. Interested vms need a way to know that shutdown is proposed and have the option to cancel shutdown.
The view models should be loosely coupled, so direct event subscriptions between them is undesirable.
How would you suggest implementing this intra-view model communication?
Would use an event aggregator to "broadcast" an account changing event be a wise approach? The event argument object would include a bool Canceled property. A subscribing vm that wants to cancel the change would set Canceled = true.
Thank you,
Ben
I think your last paragraph suggests a good solution.
Using the MVVM Light Toolkit, I would use messaging with a callback to send the message out and allow any number of subscribers to call back with a cancellation.
public class AccountSelectedMessage : NotificationMessageAction<bool>
{
public AccountSelectedMessage(Account selectedAccount, Action<bool> callback) : base("AccountSelectedWithCancelCallback", callback)
{
SelectedAccount = selectedAccount;
}
public AccountSelectedMessage(object sender, Account selectedAccount, Action<bool> callback) : base(sender, "AccountSelectedWithCancelCallback", callback)
{
SelectedAccount = selectedAccount;
}
public AccountSelectedMessage(object sender, object target, Account selectedAccount, Action<bool> callback) : base(sender, target, "AccountSelectedWithCancelCallback", callback)
{
SelectedAccount = selectedAccount;
}
public Account SelectedAccount { get; private set; }
}
public class AccountListViewModel : ViewModelBase
{
public RelayCommand<Account> AccountSelectedCommand = new RelayCommand<Account>(AccountSelectedCommandExecute);
private void AccountSelectedCommandExecute(Account selectedAccount)
{
MessengerInstance.Send(new AccountSelectedMessage(this, AccountSelectionCanceled));
}
private void AccountSelectionCanceled(bool canceled)
{
if (canceled)
{
// cancel logic here
}
}
}
public class SomeOtherViewModel : ViewModelBase
{
public SomeOtherViewModel()
{
MessengerInstance.Register<AccountSelectedMessage>(this, AccountSelectedMessageReceived);
}
private void AccountSelectedMessageReceived(AccountSelectedMessage msg)
{
bool someReasonToCancel = true;
msg.Execute(someReasonToCancel);
}
}
As you can see, this process would need to be asynchronous and take into account that you don't know how many recipients of the message could cancel, or how long they would take to respond.
An EventAggregator is the way to go. We used the one from Prism. But we didn't take all of prism. just the classes related to the EventAggregator. We did create a DomainEvent for service layers assemblies where the GUI references are not available.