-
Notifications
You must be signed in to change notification settings - Fork 0
Documentation
ReactiveProperty aims to simplify work with XAML properties.
ReactiveProperty single class for manage reactive property.
Usually you code look at (it pseudocode) this:
public Property {
get {
return m_Value;
}
set {
SetAndRaiseProperty(m_Value, value);
}
}
Issue there what Get method is symmetric, means that if user input or our code call Get it will be same, but Set method not.
User input call Set method after changes in UI (and in common case don't need to raise property) but our code call Set method before it and require raise property for UI that it also figure out changes have a place.
What because ReactiveProperty has two different behavior for two that cases:
- From application code (usually ViewModel). It means we change the value as Prop.SetValue(newValue). It executing in our code.
- From user input (or from Xaml Framework). It means user input change the value as Prop.Value = newValue. In the common case, this executing not in our code.
Simple example for reactive property:
// definition
public ReactiveProperty<int> Property { get; private set; }
//in constructor
void Constructor() {
//....
// We create ReactiveProperty with default behavior for raise property (more info below)
Property = new ReactiveProperty<int>( 1 ); // the first parameter is required and contains the initial value for the property, in our case it will be 1.
//...
}
//in command or another code where need change value for property
void changeValue() {
Property.SetValue(5); // new value will be 5 and for default behaviour property will be raised
}
It same as code above in pseudocode but you don't need write get/set section of property.
But what about change standard behavior?
Property = new ReactiveProperty<int>( 1 , raiseCallback: false );
// I set raiseCallback to false
Property.SetValue(5); // but after this line code property not be raised and UI don't change too.
//...
// maybe you need change value few times
Property.SetValue(10);
Property.SetValue(20);
//....
Property.RaiseProperty(); // to let UI know about the changes need add this line
In this example we separate set value and raise property to two lines.
You need manually call method RaiseProperty for raise property.
Usually (may be not often but sometimes) you need run some code after value is changed. For example in pseudocode above it means add code to Set method, but it can messy and Set method easy become fat. In ReactiveProperty you can use callbacks:
// I added two callback handlers: setCallback and setUICallback
Property = new ReactiveProperty<int>( 1 , setCallback: AfterSet, setUICallback: AfterUI );
// But what a heck it two?
// As I mentioned above ReactiveProperty has two behavior for UI case and ViewModel case and that's why exists two callbacks.
// setCallback - calling from ViewModel, setUICallback - calling from UI side.
private void AfterSet() {
// what need to do after code changes in ViewModel
}
private void AfterUI() {
// what need to do after code changes in UI
}
void changeValue () {
Property.SetValue(10);
//after this line there will be called AfterSet.
}
// AfterUI will be called if user change state from UI side (type character in TextBox e.g.).
Sometimes need separate Property value and logic to process it. For instance, if I have property DisplayName and I set to it the value "John Smith" maybe have a condition where I need to add "Mr" or "Ms". Of course, I can use a simple way like this:
string AddPrefix(string value) {
// some logic
}
Property.SetValue(AddPrefix(value));
ReactiveProperty suggest you another way to do this.
//in definition add getCallback callback handler
Property = new ReactiveProperty<string>( "" , getCallback: AddPrefix );
bool IsMr(string value) {
//some code
}
// all logic will be separated from SetValue place to callback handler
string AddPrefix(string currentValue) {
return (IsMr(currentValue) ? "Mr " : "Ms ") + value;
}
// set "raw" value as usual
void changeValue () {
Property.SetValue(value);
}
In this case value stay "raw" it means value will be without prefix "Mr"/"Ms".
ReactiveContext need for perform actions on multiple properties and commands
ReactiveContext it container for ReactiveProperty's and ReactiveCommand's. For attaching ReactiveProperty or ReactiveCommand you need call method Attach.
void Constructor() {
// create reactive property
Property = new ReactiveProperty<int>( 1 );
// create reactive command
Command = new ParameterlessCommand( command );
ReactiveContext reactiveContext = new ReactiveContext();
reactiveContext.Attach(Property);
reactiveContext.Attach(Command);
}
But attaching many propeties can be bored that's why you can use method AttachAll.
void Constructor() {
// create reactive property
Property = new ReactiveProperty<int>( 1 );
// create reactive command
Command = new ParameterlessCommand( command );
ReactiveContext reactiveContext = new ReactiveContext();
reactiveContext.AttachAll(this); // parameter specify object contains properties with types ReactiveProperty/ReactiveCommand
// all properties and commands will be attached automatically
}
You can raise single one property or all properties, canExecute in all commands
var reactiveContext = new ReactiveContext();
//...
reactiveContext.RaiseProperty(Property); // raise single one property
reactiveContext.RaiseAllProperties(); // raise all attached properties in reactiveContext
reactiveContext.RaiseCanExecuteCommands(); // raise all attached properties in reactiveContext
Often you have logic-related properties and/or commands.
As example you have three properties Score, Player1, Player2 and two commands AddScorePlayer1 and AddScorePlayer2.
If Player1 or Player2 changed it means that also need raise Score, canExecute in AddScorePlayer1 and AddScorePlayer2 commands.
Score = new ReactiveProperty<string>( "" );
Player1 = new ReactiveProperty<int>( 0 );
Player2 = new ReactiveProperty<int>( 0 );
AddScorePlayer1 = new ParameterlessCommand( addScorePlayer1, canAddScorePlayer1 );
AddScorePlayer2 = new ParameterlessCommand( addScorePlayer2, canAddScorePlayer2 );
void RaiseRelativeThings() {
Score.SetValue($"{Player1.Value} : {Player2.Value}");
AddScorePlayer1.RaiseCanExecuteChanged();
AddScorePlayer2.RaiseCanExecuteChanged();
}
void addScorePlayer1() {
Player1.SetValue((value) => value + 1);
RaiseRelativeThings(); // after the change, you need don't forget to raise related properties and commands
}
void addScorePlayer2() {
Player2.SetValue((value) => value + 1);
RaiseRelativeThings(); // after the change, you need don't forget to raise related properties and commands
}
In this example, you need to care about what properties/commands I need to raise after I change value.
It way can be messy and of course, you can easily forget to do it somewhere in the source code.
ReactiveContext suggest you anothet way to do this:
var reactiveContext = new ReactiveContext();
Score = new ReactiveProperty<string>( "", getCallback: DisplayScore, group: "score_group" );
Player1 = new ReactiveProperty<int>( 0, group: "score_group" );
Player2 = new ReactiveProperty<int>( 0, group: "score_group" );
AddScorePlayer1 = new ParameterlessCommand( addScorePlayer1, canAddScorePlayer1, group: "score_group" );
AddScorePlayer2 = new ParameterlessCommand( addScorePlayer2, canAddScorePlayer2, group: "score_group" );
reactiveContext.AttachAll(this);
string DisplayScore(string value) {
return Score.SetValue($"{Player1.Value} : {Player2.Value}");
}
void addScorePlayer1() {
Player1.SetValue((value) => value + 1);
reactiveContext.RaiseProperty(Player1);
}
void addScorePlayer2() {
Player2.SetValue((value) => value + 1);
reactiveContext.RaiseProperty(Player2);
}
The difference with previous source code in use ReactiveContext and adding parameter group to all properties and commands. When called code reactiveContext.RaiseProperty(PlayerX); reactive context finds all related properties and commands with the same group as passed property and raises it. In this case, you don't need to write code for raise properties and commands all work to do ReactiveContext.
ParameterlessCommand and ParameterfulCommand have same api and implement interface ICommand from System.Windows.Input.
Difference beetween it that ParameterfulCommand have parameter and Generic type in definition.
How use these classes:
void Constructor() {
// create command without parameter and without canExecute handler
// it means that command always will be active
Command = new ParameterlessCommand( command );
// create command without parameter and with canExecute handler
// it means that command will be check own activity calling canExecute method and if only it return true execute command
CommandCanExecute = new ParameterlessCommand( command2, canExecute );
// create command with parameter and without canExecute handler
// it means that command always will be active and to it will be passed parameter from xaml
ParameterCommand = new ParameterlessCommand<string>( parameterCommand );
// create command with parameter and without canExecute handler
// it means that command always will be active and to it will be passed parameter from xaml
ParameterCommandCanExecute = new ParameterlessCommand<string>( parameterCommand2, parameterCanExecute );
}
void command() {
// add command logic here
}
bool canExecute() {
return a == b ? true : false;
}
void command2() {
// add command logic here
}
void parameterCommand(string value) {
// add command logic here based on parameter value
}
void parameterCommand2(string value) {
// add command logic here based on parameter value
}
bool parameterCanExecute(string value) {
return value == "yahoo" ? true : false;
}