故事板运行得太早,或者从视图中重置单选按钮?

时间:2016-05-19 00:37:58

标签: c# wpf

我正在尝试设置一个广播组来控制单个按钮将执行的命令,并且希望广播组在执行任何其他选项后恢复为默认(第一个)选项。我知道我可以在我的视图模型中执行此操作,方法是将无线电组值绑定到属性,然后在视图模型中使用该属性。但是我会有一个命令来检查该属性是否已分配到视图模型中的相应处理程序,即使在概念上确实存在多个命令。

我宁愿保持视图模型在语义上准确,即公开单独的命令并让视图弄清楚如何执行适当的命令。例如,可能有一些替代视图实现实际上向每个命令显示用户不同的按钮,甚至以其他方式触发命令。在这种情况下,我不希望必须使视图实现代理,以便它可以在执行实际命令之前设置视图模型命令选择属性。我希望视图模型将这些命令公开为单独的不同命令。

但是我无法弄清楚如何将“执行”按钮直接绑定到视图模型的相应命令,并且一旦用户单击“执行”按钮,仍然会自动将单选按钮组的选择恢复为默认值。

我想我可以在点击按钮时激活Storyboard,其中重新检查默认RadioButton(即IsChecked设置为true)。这几乎可以工作,但是第一次点击按钮时,似乎Storyboard在命令执行之前立即完成一次,然后再次运行。这会强制执行默认命令而不是用户选择的默认命令(如果不是默认命令);显然,单选按钮状态在“执行”按钮实际执行命令之前被重置,因此执行错误的命令。

这是我的意思的简单例子:

ViewModel.cs

class ViewModel
{
    public ICommand A { get; private set; }
    public ICommand B { get; private set; }

    public ViewModel()
    {
        A = new Command("A");
        B = new Command("B");
    }

    class Command : ICommand
    {
        private readonly string _text;

        public Command(string text)
        {
            _text = text;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            MessageBox.Show("Executed " + _text);
        }
    }

}

XAML:

<Window x:Class="TestSOAskResetRadioButton.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:TestSOAskResetRadioButton"
        Title="MainWindow" Height="350" Width="525">
  <Window.DataContext>
    <l:ViewModel/>
  </Window.DataContext>

  <StackPanel>
    <StackPanel Orientation="Horizontal">
      <RadioButton x:Name="buttonA" Content="A" IsChecked="True"/>
      <RadioButton x:Name="buttonB" Content="B"/>
    </StackPanel>
    <Button Content="Execute" HorizontalAlignment="Left">
      <Button.Style>
        <Style TargetType="Button">
          <Setter Property="Command" Value="{Binding A}"/>
          <Style.Triggers>
            <DataTrigger Binding="{Binding IsChecked, ElementName=buttonB}" Value="True">
              <Setter Property="Command" Value="{Binding B}"/>
            </DataTrigger>
            <EventTrigger RoutedEvent="Click">
              <BeginStoryboard>
                <Storyboard Storyboard.Target="{x:Reference Name=buttonA}"
                            Storyboard.TargetProperty="IsChecked" BeginTime="0:0:5">
                  <BooleanAnimationUsingKeyFrames Duration="0">
                    <DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
                  </BooleanAnimationUsingKeyFrames>
                </Storyboard>
              </BeginStoryboard>
            </EventTrigger>
          </Style.Triggers>
        </Style>
      </Button.Style>
    </Button>
  </StackPanel>
</Window>

我将BeginTime的{​​{1}}属性设置为5秒,以夸大我所看到的行为,因此更为明显。无论Storyboard值如何,第一次单击“执行”按钮时,会立即重新选择默认单选按钮,随后单击该按钮时,一切正常(即使BeginTime是0)。

要重现此问题,只需运行程序,单击“B”单选按钮,然后单击“执行”按钮。将立即选择“A”单选按钮,并执行“A”命令。

单击“执行”按钮一次,如果等待BeginTime值过去然后再试一次 - 即选择单选按钮“B”并单击“执行”按钮 - 它可以工作罚款(在第一次尝试后立即重新选择“B”按钮,可以很容易地看到BeginTime何时结束,因为动画确实将选择重置为在该时间点按下“A”)

基本上:它似乎第一次运行时,BeginTime立即将目标属性设置为其最终值,然后运行完成,再次将其设置为 。在任何后续运行中,它会单独保留目标属性,直到“执行”按钮有机会执行绑定命令,只设置目标属性一次。

如何避免单选按钮过早地设置回选定状态?


如果有一种更好的方法,在不涉及视图模型的情况下,对于视图在命令执行后重置单选按钮状态,那也是一个很好的答案。我发现以这种方式使用Storyboard有点hacky,至少可以说; “动画”一些东西,所以我可以将一个值切换回原来的状态似乎有点矫枉过正。只是我不知道任何其他方法来解决这个问题,同时仍然将逻辑限制在视图中。

附录:我在发布问题后意识到,使用Storyboard的延迟操作的经典后备/黑客在这种情况下会起作用。我可以处理Dispatcher.InvokeAsync()事件,并在处理程序中使用该方法来调用设置Button.Click属性的语句。这有点棘手,因为在我的真实场景中,这些元素实际上是在IsChecked中声明的,因此我必须调用DataTemplate来获取我需要重新设置的FrameworkElement.FindName() (而不是,例如使用XAML生成的对象的字段名称)。但这似乎工作正常。

我仍然非常喜欢解释RadioButton行为的答案,特别是如果有什么关于它我做错了,当然任何其他合理的解决办法都会受到欢迎(特别是如果他们比我已经拥有的更好。

1 个答案:

答案 0 :(得分:1)

在此示例中,单选按钮选择新命令,按下按钮执行新命令。执行后,该命令将恢复为默认命令。

这是一个ViewModel,它具有不同的命令和所选命令的属性。

public class MultiCommandViewModel : INotifyPropertyChanged
{
    private object value;
    private ICommand selectedCommand;

    public MultiCommandViewModel()
    {
        SetValueToXCommand = new RelayCommand((obj) => setValue(obj), (obj) => true);
        SetValueToNameCommand = new RelayCommand((obj) => setValue("Michael"), (obj) => true);
        SetValueTo1000Command = new RelayCommand((obj) => setValue(1000), (obj) => true);
        SetSelectedCommand = new RelayCommand((obj) => SelectedCommand = obj as ICommand, (obj) => true);
    }

    private void setValue(object obj)
    {
        Value = obj;
        SelectedCommand = null;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void Notify([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    public object Value
    {
        get { return value; }
        set
        {
            this.value = value;
            Notify();
        }
    }

    public ICommand SelectedCommand
    {
        get { return (selectedCommand == null) ? SetValueToNameCommand : selectedCommand; }
        set
        {
            selectedCommand = value;
            Notify();
        }
    }

    public RelayCommand SetValueToXCommand { get; private set; }
    public RelayCommand SetValueToNameCommand { get; private set; }
    public RelayCommand SetValueTo1000Command { get; private set; }
    public RelayCommand SetSelectedCommand { get; private set; }
}

这是View(没有代码或触发/故事板)

<UserControl.DataContext>
    <ViewModels:MultiCommandViewModel />
</UserControl.DataContext>

<StackPanel>

    <RadioButton Content="Set to Name Command"
                 GroupName="selectCommand"
                 Command="{Binding SetSelectedCommand}"
                 CommandParameter="{Binding SetValueToNameCommand}"
                 Margin="12" 
                 IsChecked="True"/>

    <RadioButton Content="Set to 1000 Command"
                 GroupName="selectCommand"
                 Command="{Binding SetSelectedCommand}"
                 CommandParameter="{Binding SetValueTo1000Command}"
                 Margin="12" />

    <RadioButton Content="Set to (X) Command"
                 GroupName="selectCommand"
                 Command="{Binding SetSelectedCommand}"
                 CommandParameter="{Binding SetValueToXCommand}"
                 Margin="12" />

    <StackPanel Margin="12">
        <TextBlock Text="Enter X Value" />
        <TextBox Name="TextBoxValue"
                 Text="Dummy Value" />
    </StackPanel>

    <Button Content="Execute Selected Command"
            Command="{Binding SelectedCommand}"
            CommandParameter="{Binding Text, ElementName=TextBoxValue}"
            Margin="12" />

    <StackPanel Margin="12">
        <TextBlock Text="Value" />
        <TextBlock Text="{Binding Value}" />
    </StackPanel>

</StackPanel>

当您选择单击该按钮时将执行的单选按钮时,之后该命令将恢复为按钮中的默认命令。注意:单选按钮没有绑定来重新选择默认命令,因为这是一个精简的示例。

Start Up

点击按钮......

Button Click

选择'设置为(X)命令,将值更改为'Hello World'并按下按钮。

Button Click after changing command selection

依旧......