wpfcaliburn.micro

Caliburn.Micro how to write a extract a action to resource so that other similar element can reuse it?


I am writing a simple calculator program in WPF,and used Caliburn.Micro MVVM framework.I defined the UI in XMAL like this

<Button Name="Clear" Grid.Row="0" Grid.Column="0" Style="{StaticResource grey_button}" Content="C"/>
<Button Grid.Row="0" Grid.Column="1" Style="{StaticResource grey_button}" Content="+/-"/>
<Button Name="Remainder" Grid.Row="0" Grid.Column="2" Style="{StaticResource grey_button}" Content="%"/>
<Button Name="Division" Grid.Row="0" Grid.Column="3" Style="{StaticResource blue_button}" Content="÷"/>

<Button Name="Seven" Grid.Row="1" Grid.Column="0" Style="{StaticResource white_button}" Content="7"/>
<Button Name="Eight" Grid.Row="1" Grid.Column="1" Style="{StaticResource white_button}" Content="8"/>
<Button Name="Nine" Grid.Row="1" Grid.Column="2" Style="{StaticResource white_button}" Content="9"/>
<Button Name="Multiply" Grid.Row="1" Grid.Column="3" Style="{StaticResource blue_button}" Content="x"/>

<Button Name="Four" Grid.Row="2" Grid.Column="0" Style="{StaticResource white_button}" Content="4"/>
<Button Name="Five" Grid.Row="2" Grid.Column="1" Style="{StaticResource white_button}" Content="5"/>
<Button Name="Six" Grid.Row="2" Grid.Column="2" Style="{StaticResource white_button}" Content="6"/>
<Button Name="Plus" Grid.Row="2" Grid.Column="3" Style="{StaticResource blue_button}" Content="+"/>

<Button Name="One" Grid.Row="3" Grid.Column="0" Style="{StaticResource white_button}" Content="1">
</Button>
<Button Name="Two" Grid.Row="3" Grid.Column="1" Style="{StaticResource white_button}" Content="2"/>
<Button Name="Three" Grid.Row="3" Grid.Column="2" Style="{StaticResource white_button}" Content="3"/>
<Button Name="Subtract" Grid.Row="3" Grid.Column="3" Style="{StaticResource blue_button}" Content="-" FontSize="25"/>

<Button Name="Dot" Grid.Row="4" Grid.Column="0" Style="{StaticResource white_button}" Content="·"/>
<Button Name="Zero" Grid.Row="4" Grid.Column="1" Style="{StaticResource white_button}" Content="0"/>
<Button Name="Backward" Grid.Row="4" Grid.Column="2" Style="{StaticResource white_button}" Content="x"/>
<Button Name="Equal" Grid.Row="4" Grid.Column="3" Style="{StaticResource blue_button}" Content="="/>

In this way,i actually can handle the click action because of Caliburn naming Conversion as i define One,Two,Three .... method in my ViewModel

public void Zero()
{
    Input += "0";
}

public void One(string content)
{
    Input += "1";

}

public void Two()
{
    Input += "2";
}

public void Three()
{
    Input += "3";
}

public void Four()
{
    Input += "4";
}

Now i want to handle all input action with one method

public void Input(string content)
{
   Input+=content;
}

So i imported Microsoft.Xaml.Behaviors as namespace b in the top of view,and defined the following event in one of my button


<Button Name="One" Grid.Row="3" Grid.Column="0" Style="{StaticResource white_button}" Content="1">
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="Click">
            <cal:ActionMessage MethodName="One">
                <cal:Parameter Value="{Binding ElementName=One, Path=Content}"></cal:Parameter>
            </cal:ActionMessage>
        </b:EventTrigger>
    </b:Interaction.Triggers>
</Button>


Problmen: I want to reuse the code about Triggers,and I know i should extract this trigger logic to resouce,and i can used it like this:

 <Button Name="One" Style="{StaticResource blue_button}" Content="1" Action={StaticResource InputAction,Parameter=Binding{ElementName=One Path=Content}}/>

 <Button Name="Two" Style="{StaticResource blue_button}" Content="2" Action={StaticResource InputAction,Parameter=Binding{ElementName=Two Path=Content}}/>

 <Button Name="Three" Style="{StaticResource blue_button}" Content="3" Action={StaticResource InputAction,Parameter=Binding{ElementName=Three Path=Content}}/>

...

 <Button Name="Plus" Style="{StaticResource blue_button}" Content="+" Action={StaticResource InputAction,Parameter=Binding{ElementName=Plus Path=Content}}/>


Solution

  • The main way to set the action of the button is to use commands. In a typical Solution with GUI on WPF, it is common to implement the MVVM pattern. In this case, commands are set in the ViewModel. You can pass a parameter to the commands for its further processing. In a very simplified form, I will show you the implementation in which the ViewModel also performs the functions of the Model.

    The example uses my implementation of ViewModelBase . You can replace it with any convenient for you. Or, if you are interested in my implementation, you can take it from this repository: https://github.com/INexteR/NewTrade/tree/main/NewTradeSln/ViewModels

    using Simplified;
    using System.Collections.ObjectModel;
    
    namespace Core2024.SO.huojian.question78746674
    {
        public class CalculatorViewModel : ViewModelBase
        {
            public static ReadOnlyCollection<string> ButtonNames { get; }
                = Array.AsReadOnly("C, +/-, %, ÷, ✖, +, -, ., =, 🠔, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...Any Names"
                                    .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
    
            public CalculatorViewModel()
            {
                DisplayedValue = "0";
            }
    
            public RelayCommand CalculatorCommand => GetCommand<string>(CalculatorExecute, CalculatorCanExecute);
    
            private bool CalculatorCanExecute(string parameter)
                => ButtonNames.Contains(parameter);
    
            public string DisplayedValue { get => Get<string>(); private set => Set(value); }
    
            private void CalculatorExecute(string parameter)
            {
                string value = DisplayedValue;
                switch (parameter)
                {
                    case "0":
                    case "1":
                    case "2":
                    case "3":
                    case "4":
                    case "5":
                    case "6":
                    case "7":
                    case "8":
                    case "9":
                        if (value == "0")
                            value = string.Empty;
                        value = value + parameter;
                        break;
                    case "C":
                        value = "0";
                        break;
                    case "+/-":
                        if (value != "0")
                        {
                            if (value[0] == '-')
                                value = value.Substring(1);
                            else
                                value = "-" + value;
                        }
                        break;
    
                    // Logic of other buttons
    
                    default:
                        break;
                }
                DisplayedValue = value;
            }
    
        }
    }
    
    <Window x:Class="Core2024.SO.huojian.question78746674.CalculatorWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Core2024.SO.huojian.question78746674"
            mc:Ignorable="d"
            Title="CalculatorWindow" Height="450" Width="800"
            SizeToContent="WidthAndHeight"
            DataContext="{DynamicResource vm}">
        <Window.Resources>
            <local:CalculatorViewModel x:Key="vm"/>
            <Style x:Key="base" TargetType="FrameworkElement">
                <Setter Property="Margin" Value="5"/>
            </Style>
            <Style x:Key="button.command" TargetType="Button" BasedOn="{StaticResource base}">
                <Setter Property="CommandParameter" Value="{Binding Content, RelativeSource={RelativeSource Self}}"/>
                <Setter Property="Command" Value="{Binding CalculatorCommand}"/>
                <Setter Property="Padding" Value="15 5"/>
            </Style>
            <Style x:Key="button.white_button" TargetType="Button" BasedOn="{StaticResource button.command}">
                <Setter Property="Background" Value="White"/>
                <Setter Property="Foreground" Value="Blue"/>
            </Style>
            <Style x:Key="button.blue_button" TargetType="Button" BasedOn="{StaticResource button.command}">
                <Setter Property="Background" Value="Blue"/>
                <Setter Property="Foreground" Value="White"/>
            </Style>
        </Window.Resources>
        <StackPanel>
            <TextBox Text="{Binding DisplayedValue, Mode=OneWay}" Style="{StaticResource base}" IsReadOnly="True"
                     HorizontalContentAlignment="Right"
                     Padding="15 5"/>
            <UniformGrid Columns="4">
                <Button Style="{StaticResource button.blue_button}" Content="C"/>
                <Button Style="{StaticResource button.blue_button}" Content="+/-"/>
                <Button Style="{StaticResource button.blue_button}" Content="%"/>
                <Button Style="{StaticResource button.blue_button}" Content="÷"/>
    
                <Button Style="{StaticResource button.white_button}" Content="7"/>
                <Button Style="{StaticResource button.white_button}" Content="8"/>
                <Button Style="{StaticResource button.white_button}" Content="9"/>
                <Button Style="{StaticResource button.blue_button}" Content="✖"/>
    
                <Button Style="{StaticResource button.white_button}" Content="4"/>
                <Button Style="{StaticResource button.white_button}" Content="5"/>
                <Button Style="{StaticResource button.white_button}" Content="6"/>
                <Button Style="{StaticResource button.blue_button}" Content="+"/>
    
                <Button Style="{StaticResource button.white_button}" Content="1"/>
    
                <Button Style="{StaticResource button.white_button}" Content="2"/>
                <Button Style="{StaticResource button.white_button}" Content="3"/>
                <Button Style="{StaticResource button.blue_button}" Content="-" FontSize="25"/>
    
                <Button Style="{StaticResource button.white_button}" Content="·"/>
                <Button Style="{StaticResource button.white_button}" Content="0"/>
                <Button Style="{StaticResource button.blue_button}" Content="🠔"/>
                <Button Style="{StaticResource button.blue_button}" Content="="/>
            </UniformGrid>
        </StackPanel>
    </Window>
    

    enter image description here