Spooksoft.VisualStateManager
1.0.7.5
See the version list below for details.
dotnet add package Spooksoft.VisualStateManager --version 1.0.7.5
NuGet\Install-Package Spooksoft.VisualStateManager -Version 1.0.7.5
<PackageReference Include="Spooksoft.VisualStateManager" Version="1.0.7.5" />
<PackageVersion Include="Spooksoft.VisualStateManager" Version="1.0.7.5" />
<PackageReference Include="Spooksoft.VisualStateManager" />
paket add Spooksoft.VisualStateManager --version 1.0.7.5
#r "nuget: Spooksoft.VisualStateManager, 1.0.7.5"
#:package Spooksoft.VisualStateManager@1.0.7.5
#addin nuget:?package=Spooksoft.VisualStateManager&version=1.0.7.5
#tool nuget:?package=Spooksoft.VisualStateManager&version=1.0.7.5
VisualStateManager
VisualStateManager contains a set of classes - Commands and Conditions - which simplify managing commands and their dependencies in Windows Presentation Foundation application.
The problem
The common issue is that commands, that you create have their dependencies. For instance, a Save command should not be available if document has already been saved or Delete command should not be available when no item is selected.
If you try to manage enable-state of your commands directly from the place where they might change, it may lead to a lot of messy and hard-to-maintain code, for instance:
public void SaveDocument()
{
// Perform action
SaveDocumentAction.Enabled = false;
}
public void HandleDocumentChanged()
{
SaveDocumentAction.Enabled = true;
}
public void SelectionChanged()
{
CopyCommand.Enabled = selection != null;
CutCommand.Enabled = selection != null;
SearchInSelectionCommand.Enabled = selection != null && selection.IsRegular;
// ...
}
There are numerous problems with this approach:
- If you create a new action, you have to manually find all places, when its enable-state might change and add appropriate check there;
- If it happens, that there is an additional condition for a command, which may influence its enable-state, then you have to re-visit all places, where this state is evaluated and modify them;
- Methods, which are supposed to perform actions are aware of the whole window's or control's architecture (because they know, which actions may they influence)
- It is very easy to miss setting the enable-state, which leads to hard-to-find errors.
The solution
Spooksoft.VisualStateManager solves the problem by doing two things:
- Encapsulating a condition under which command may run into a class, and
- Reversing the logic: instead of methods setting availability of actions, actions now listen to changes of application state and change their availability automatically.
Usage
Spooksoft.VisualStateManager provides an AppCommand class, which implements ICommand interface and a set of classes, which allows you to handle condition changes with ease.
Simple case
This is the simplest way you can use Commands and Conditions:
private readonly SimpleCondition documentSavedCondition;
public ICommand SaveDocumentCommand { get; }
public MyWindow()
{
documentSavedCondition = new SimpleCondition(false);
SaveDocumentCommand = new AppCommand(obj => DoSaveDocument, !documentSavedCondition); // Note the !
}
public void SaveDocument()
{
// Perform save
documentSavedCondition.Value = true;
}
public void HandleDocumentChanged()
{
documentSavedCondition.Value = false;
}
Note, that SaveDocument and HandleDocumentChanged methods no longer worry about specific actions, which should be updated. Instead, they modify value of the condition and all actions, which depends on it will adjust accordingly.
AppCommand
The AppCommand class provides a simple implementation of ICommand interface and provides infrastructure required for listening to SimpleCondition value changes. You need to provide an Action<object>, which will be called when the command executes and - optionally - a SimpleCondition, which will control its enable-state.
SimpleCondition
The simplest condition wraps a bool into a class and notifies about changes. The usage is very simple.
myCondition = new SimpleCondition(false); // Initial value
myCommand = new AppCommand(obj => SomeMethod(), myCondition);
// ...
myCondition.Value = true; // Will propagate to all commands
CompositeCondition
The CompositeCondition allows you to combine different conditions into one with "or" and "and" boolean operators.
You may define it directly, like:
myCondition = new CompositeCondition(CompositionKind.And, myOtherCondition1, myOtherCondition2);
myCommand = new AppCommand(obj => SomeMethod(), myCondition);
But since BaseCondition overloads | and & operators, it is far easier to do it like following:
myCommand = new AppCommand(obj => SomeMethod(), myOtherCondition1 & myOtherCondition2);
The latter is also a far easier to read.
NegateCondition
You can negate value of some condition. Similarly to CompositeCondition you may do it in two ways:
myCondition = new NegateCondition(myOtherCondition);
myCommand = new AppCommand(obj => SomeMethod(), myCondition);
Or simply:
myCommand = new AppCommand(obj => SomeMethod(), !myOtherCondition);
PropertyWatchCondition
This condition allows you to automatically track a property of some object, provided that object in question implements INotifyPropertyChanged interface and properly informs about property value change.
Usage:
myCondition = new PropertyWatchCondition<WatchedObject>(watchedObjectInstance, ob => ob.SomeProperty, false);
The last parameter defines default value in case watched object instance was null.
SwitchCondition
SwitchCondition behaves somewhat as a switch statement. It exposes a series of internal conditions, which are set basing on current value. Example should explain it better:
myCondition = new SwitchCondition<int>(1, 2, 3, 4);
myCommand1 = new AppCommand(() => SomeMethod(), myCondition.Conditions[1]);
myCommand2 = new AppCommand(() => SomeMethod(), myCondition.Conditions[2]);
myCondition.Current = 2; // myCommand1 will be disabled and myCommand2 will be enabled
ChainedLambdaCondition
Powerful condition, which allows to specify a series of expressions, which can traverse a couple of classes and define a boolean expression at the end. For example:
myCondition = new ChainedLambdaCondition<MainViewModel, DocumentsManager, Document>(this,
mvm => mvm.DocumentsManager,
dm => dm.CurrentDocument,
cd => cd.Highlighting == Highlightings.Xml,
false);
ChainedLambdaCondition has the following requirements/restrictions:
- Only single-member accesses are allowed. So instead
x => x.A.Bwritex => x.A, a => a.B. - Currently it supports only three-levels of nesting. If you need more, contact me, I'll add more (or look into sources how to do it yourself)
- Classes on every level must implement
INotifyPropertyChangedinterface, so thatChainedLambdaExpressioncan listen to property value changes.
The upsides of ChainedLambdaCondition are:
- It automatically tracks all members on its way, including multiple member accesses (as long as they are single-level):
x => x.A + x.B > 5 - It allows you to clearly express logic behind the condition, what simplifies reading the source code a log.
- It behaves properly if value on any level is null - in such case the default value is used.
LambdaCondition
Another powerful condition, which allows you to define a single lambda, which defines, when condition is met and when not. Its usage is simpler than ChainedLambdaCondition, but it is also a little bit more restricted.
myCondition = new LambdaCondition<MainViewModel>(this, mvm => mvm.DocumentsManager.CurrentDocument.Highlighting == Highlighting.Xml, false);
LambdaCondition differs from ChainedLambdaCondition in the following ways:
- Multiple member accesses are allowed, you can write
x => x.A.B - There is no restriction on nesting levels, if you really want / need, you can write eg.
x => A.B.C.D.E.F.G.H.I.J.K.L - Since
LambdaConditionactively checks for nulls on the way, there are some restrictions on operations, which you can perform. Namely, you cannot doascasts or call methods. You can however use most operators. If you need to create more complex condition, useChainedLambdaConditioninstead, which has less restrictions. - It is also required for every instance in the member access chain to implement
INotifyPropertyChangedinterface.
The upsides of LambdaCondition are:
- You can write complex conditions reaching various members (and sub-members) of your viewmodels
- It automatically tracks all members on its way, including multiple member accesses:
x => x.A.B + x.C.D > 5 - It autonatically tracks nulls in the member access chains - in such case it falls back to the default value.
- It allows you to define logic behind condition even better than
ChainedLambdaCondition.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET Framework | net461 is compatible. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
This package has no dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Spooksoft.VisualStateManager:
| Package | Downloads |
|---|---|
|
Spooksoft.VisualStateManager.Reactive
Extensions for Commands and Conditions for usage with Reactive |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.2.1 | 455 | 5/24/2023 |
| 1.2.0 | 351 | 3/7/2023 |
| 1.1.0 | 474 | 1/31/2023 |
| 1.0.8.2 | 4,639 | 5/25/2022 |
| 1.0.8.1 | 758 | 3/22/2022 |
| 1.0.8 | 575 | 3/22/2022 |
| 1.0.7.6 | 441 | 12/29/2021 |
| 1.0.7.5 | 5,697 | 11/24/2021 |
| 1.0.7.4 | 464 | 11/19/2021 |
| 1.0.7.3 | 500 | 11/19/2021 |
| 1.0.7.2 | 512 | 11/19/2021 |
| 1.0.7.1 | 508 | 11/19/2021 |
| 1.0.7 | 527 | 11/19/2021 |
| 1.0.6 | 448 | 11/18/2021 |
| 1.0.5 | 467 | 11/18/2021 |
| 1.0.4 | 477 | 10/27/2021 |
| 1.0.3 | 492 | 10/27/2021 |
| 1.0.2 | 842 | 1/26/2021 |
| 1.0.1 | 608 | 10/29/2020 |
| 1.0.0 | 553 | 10/29/2020 |
Reintroduced old LambdaCondition as ChainedLambdaCondition.