Implementing Commands in MVUX
Outcome: A public method on your MVUX model shows up in the ViewModel as a command and can be bound to a <Button>.
Requires the MVUX UnoFeature.
When to use
- You have a model method like
Save()orDoWork()and you just want a button to call it. - You don't want to hand-write an
ICommand.
Steps
Create the model method
using Uno.Extensions.Reactive; using System.Threading; public partial record MainModel { public void Save() { // save logic } }- MVUX sees
public void Save()and generates a command for the ViewModel namedSave. - Generated type is an
IAsyncCommandeven forvoidmethods, so the button auto-disables while it runs.
- MVUX sees
Bind in XAML
<Button Content="Save" Command="{Binding Save}" />- Binding is to the generated ViewModel, not to the model method directly.
- When clicked, MVUX calls
MainModel.Save().
Notes
- Applies to:
void,Task,ValueTaskmethods. (Uno Platform) - If the method is async, add an optional
CancellationTokenas last argument – MVUX will cancel it if the VM is disposed. (Uno Platform)
Run an async model method and auto-disable the button
Outcome: Button disables while your async method runs.
Steps
Async method on the model
public partial record MainModel { public async ValueTask LoadData(CancellationToken ct) { await Task.Delay(1000, ct); // load or refresh } }Bind to generated command
<Button Content="Refresh" Command="{Binding LoadData}" />What MVUX does
- Generates an
IAsyncCommand LoadData { get; }on the bindable ViewModel. - While
LoadDataruns, the command reports “busy”, so the button is disabled. (Uno Platform)
- Generates an
Notes
- Works for
TaskandValueTask.
Pass a value from the view to the model command
Outcome: You click a button and the model method receives the button's CommandParameter.
Steps
Model accepts a parameter
public partial record MainModel { public void DoWork(double amount) { // use amount } }- MVUX generates
public IAsyncCommand DoWork { get; }.
- MVUX generates
View sends the parameter
<Slider x:Name="AmountSlider" Minimum="1" Maximum="100" /> <Button Content="Apply" Command="{Binding DoWork}" CommandParameter="{Binding Value, ElementName=AmountSlider}" />Command rules
- MVUX first checks it can cast the parameter to
double. - If parameter is
nullor can’t be cast → command is disabled. (Uno Platform)
- MVUX first checks it can cast the parameter to
Notes
You can still add a
CancellationTokenas the last argument:public ValueTask DoWork(double amount, CancellationToken ct) { ... }View’s
CommandParameteris ignored if the model method doesn’t declare a matching parameter. (Uno Platform)
Use the latest feed value when a command runs
Outcome: Your method parameter is auto-filled with the current value of a feed on the same model.
When to use
- You have
public IFeed<int> CounterValue => ...; - You call
ResetCounter(int counterValue)and want MVUX to inject the current counter.
Steps
Model with feed
using Uno.Extensions.Reactive; public partial record CounterModel { public IFeed<int> CounterValue => /* feed source */; public void ResetCounter(int counterValue) { // counterValue is the current value of CounterValue } }Why it works
- MVUX looks for a method parameter whose name and type matches a feed property.
- Name matching is not case-sensitive. (Uno Platform)
- It injects the generic type of the feed (
int), notIFeed<int>. (Uno Platform)
Bind in XAML
<Button Content="Reset" Command="{Binding ResetCounter}" />
Explicit feed mapping
If the names don’t match:
public IFeed<int> CounterValue => ...;
[ImplicitFeedCommandParameter(false)]
public void ResetCounter([FeedParameter(nameof(CounterValue))] int newValue)
{
// newValue is CounterValue's current value
}
ImplicitFeedCommandParameter(false)= “don’t auto-match”.[FeedParameter]= “but do match this one”. (Uno Platform)
Stop MVUX from generating commands
Outcome: Public methods stay as methods on the ViewModel; commands are not generated.
Option A – turn off on a method
public partial record MyModel
{
[Command(false)]
public async ValueTask Save()
{
// will be exposed as a method on the bindable VM
}
}
Use this when you want to x:Bind to the method directly:
<Button Click="{x:Bind Save}" />
Option B – turn off on a class
[ImplicitCommands(false)]
public partial record MyModel
{
public void DoWork() { ... } // no command generated
}
Option C – turn off for the whole assembly
using Uno.Extensions.Reactive;
[assembly: ImplicitCommands(false)]
- After this, only methods marked with
[Command]will get a command. (Uno Platform)
Force MVUX to generate a command
Outcome: Even if commands are globally disabled, this method still becomes a command.
Steps
Disable globally
[assembly: ImplicitCommands(false)]Opt-in per method
public partial record MyModel { [Command] // default is true public async ValueTask DoWork() { // ... } }Use in XAML
<Button Command="{Binding DoWork}" Content="Run" />
[Command]wins over[ImplicitCommands]. (Uno Platform)
Run a command when a control event happens
Outcome: An event like DoubleTapped can trigger a generated command and pass a parameter.
Requires:
Uno.Microsoft.Xaml.Behaviors.Interactivity.WinUIUno.Microsoft.Xaml.Behaviors.WinUI.Managed
Steps
Model method
public partial record MyModel { public void TextBlockDoubleTapped(string text) { // handle double tap } }- MVUX generates
IAsyncCommand TextBlockDoubleTapped { get; }
- MVUX generates
XAML with behaviors
<Page xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"> <TextBlock x:Name="TitleBlock" Text="Double-tap me"> <interactivity:Interaction.Behaviors> <interactions:EventTriggerBehavior EventName="DoubleTapped"> <interactions:InvokeCommandAction Command="{Binding TextBlockDoubleTapped}" CommandParameter="{Binding Text, ElementName=TitleBlock}" /> </interactions:EventTriggerBehavior> </interactivity:Interaction.Behaviors> </TextBlock> </Page>
- When the event fires, MVUX runs the command and passes the text. (Uno Platform)
Create a command in code (no generation)
Outcome: You hand-build a command in the model for special scenarios.
When to use
- You need a command that doesn't come from a public method.
- You want fine control over what
CanExecuteor parameters are.
Example
using Uno.Extensions.Reactive;
using System.Threading;
using System.Windows.Input;
public partial record MyModel
{
public ICommand PingServerCommand =>
Command.Async(async ct => await PingServer(ct));
private ValueTask PingServer(CancellationToken ct)
{
// ping logic
return ValueTask.CompletedTask;
}
}
Command.Async(...)creates anIAsyncCommand.Bind from XAML:
<Button Content="Ping" Command="{Binding Model.PingServerCommand}" />Note: for manually created commands in the model, you bind to
Model.<Command>from the ViewModel. (Uno Platform)
Create a command that uses feed value + can-execute + async work
Outcome: Command takes the current feed value, checks a condition, then runs.
Example
public partial record PagerModel
{
public IFeed<int> CurrentPage => /* ... */;
public IAsyncCommand GoToPageCommand =>
Command.Create(builder =>
builder
.Given(CurrentPage) // materialize latest page
.When(page => page > 0) // only if valid
.Then(async (page, ct) => await NavigateToPage(page, ct))
);
private ValueTask NavigateToPage(int page, CancellationToken ct)
{
// navigate
return ValueTask.CompletedTask;
}
}
XAML
<Button
Content="Go"
Command="{Binding Model.GoToPageCommand}" />
- Because it’s explicit, it’s not auto-copied on the generated ViewModel – so we bind through
Model.. (Uno Platform)
Understand MVUX command generation rules
Outcome: You know which methods become commands and which don’t.
Rules (Uno Platform)
- Public method on the model.
- Method returns
void,Task, orValueTask. (Return values are ignored.) - Optional one
CancellationTokenat the end. - Optional one view-supplied parameter (from
CommandParameter). - Other parameters can be feed-resolved (by name+type).
- Generation can be turned off (
[ImplicitCommands(false)]or[Command(false)]). - Generation can be forced (
[Command]).
What you get
public IAsyncCommand MethodName { get; }