【问题标题】:How to Set a Control's user-editable property with a Button click only in XAML如何仅在 XAML 中通过单击按钮设置控件的用户可编辑属性
【发布时间】:2016-03-08 16:19:47
【问题描述】:

具体来说,我正在尝试创建一个“重置”按钮,它将滑块的值设置为零。我不想要代码隐藏,因为模板可以自定义。

我尝试了以下方法。如果没有FillBehavior="Stop",用户将无法再移动滑块。使用FillBehavior="Stop",该值立即返回到前一个值(看起来它根本什么都不做)

    <Slider Name="MySlider" Minimum="-5" Maximum="5" Value="{Binding FloatProperty}"></Slider>
    <Button Content="Reset">
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">
                <BeginStoryboard>
                    <Storyboard FillBehavior="Stop">
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MySlider" Storyboard.TargetProperty="Value">
                            <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Button.Triggers>
    </Button>

我还尝试在 Slider 上使用 DataTrigger 绑定到按钮的 IsPressed 属性,但我也无法让它工作。它会返回到之前的值。

请注意,滑块的值绑定到数据的属性。

谢谢!

【问题讨论】:

  • 我对您对动画的使用有点困惑 - 动画通常意味着 “在 C 的时间段内将目标属性从 A 转移到 B”,而什么您要做的只是“将绑定值设置为X”。根据此 XAML 所代表的内容(包含 Slider 和 Button 的 UserControl、绑定到特定 DataContext 的松散 XAML 等),我将使用 ICommand 来重置值,或者只使用后面的代码。如果我在模板中有一个 Click 事件并且只打算修改 UI 特定的值,我认为使用文件隐藏代码没有任何问题。
  • @Rachel,Animation 被选中是因为它几乎可以工作,并且符合避免对该模板显示的底层类进行少量更改的标准。是的,目的是允许通过松散的 xaml 进行蒙皮,如果可以通过 xaml 更改值,那么定制器可以定义“重置”的含义等等。
  • @user2149714 这完全是一个错误。滑块冻结,不响应鼠标点击,有界值。
  • @AnjumSKhan - 不是错误。请看我的解释。

标签: wpf xaml eventtrigger


【解决方案1】:

对您的 XAML 进行少量编辑似乎可以解决问题。您需要按特定源名称过滤 Button.Click 事件,如下所示。此外,从 Slider.Value 中删除 Binding 似乎有帮助。

我不太确定如何最好地维护绑定并仍然能够使其正常工作。

<Slider Name="MySlider"
        Maximum="5"
        Minimum="-5"/>
<Button Name="myButton" Content="Reset">
    <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click" SourceName="myButton">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Duration="0"
                                     Storyboard.TargetName="MySlider"
                                     Storyboard.TargetProperty="Value"
                                     To="0" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

如果目标是将 Slider.Value 数据绑定到某物,例如...

<Slider Value="{Binding FloatProperty}"/>

...然后我怀疑(尽管我不是 100% 确定)您尝试做的事情可能是不可能的。

首先,让我解释一下为什么您会看到滑块被“冻结”。这不是一个错误 - 我只需要仔细阅读文档以了解发生了什么。

当 FillBehavior 设置为 Stop 时,当 TimeLine 完成时,Animation 将恢复该值(恢复到 Animation 开始之前的值)。

另一方面,当 FillBehavior 为 HoldEnd(这是您在未明确指定 FillBehavior 时获得的默认行为)时,DoubleAnimation 将继续保持结束值并防止来自其他来源(如滑块手动移动)。因此,每次移动滑块时,Animation 都会将其重置为 0,因为 HoldEnd。这就是为什么您会看到滑块在 0 时表现得好像“冻结”了一样。

我调查了在 Button.IsPressed = True 上使用 DataTrigger 作为替代方案。这通常在 Style 或 ControlTemplate 中完成 - 它看起来像这样:

         <Slider Maximum="5"
                Minimum="-5"
                Value="{Binding FloatProperty}">
            <Slider.Template>
                <ControlTemplate TargetType="Slider">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Slider x:Name="Slider"
                                Grid.Row="0"
                                Maximum="{TemplateBinding Maximum}"
                                Minimum="{TemplateBinding Minimum}"
                                Value="{Binding RelativeSource={RelativeSource TemplatedParent},
                                                Path=Value,
                                                Mode=TwoWay}" />
                        <Button x:Name="ResetButton"
                                Grid.Row="1"
                                Content="Reset" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding ElementName=ResetButton, Path=IsPressed}" Value="True">
                            <Setter TargetName="Slider" Property="Value" Value="0" />
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Slider.Template>
        </Slider>

不幸的是,Dependency Property Value Precedence rules 认为 TemplatedParent 模板属性的优先级高于触发器和样式设置器。因此,单击“重置”按钮会暂时将滑块值更改为 0,但会立即重置为相应绑定支持的原始值。

在用尽了 Animation 和 DataTriggers 之后,我得出结论认为这可能是不可能的。也就是说,鉴于 WPF 中存在的大量功能,我很可能是错的......


编辑:2015 年 12 月 5 日

我想我有一个解决方案可以满足您的关键要求 - 即允许设计师修改 UI,包括“重置”滑块值的含义。也就是说,此解决方案需要代码隐藏 - 但这样的代码隐藏不会阻止 XAML 充分表达您的意图。

这个想法是写一个行为,并使用该行为将按钮与滑块相关联,并指示此按钮应触发“重置”操作。

行为的基本轮廓如下所示:

    /// <remarks>
    /// Requires System.Windows.Interactivity.dll to be 
    /// added to the References section of the Project
    /// </remarks>
    public class SliderResetBehavior: Behavior<Button>
    {
        protected override void OnAttached()
        {
            // AssociatedObject refers to a Button instance
            // to which this Behavior is attached
            AssociatedObject.Click += OnClick;
        }

        /// <summary>
        /// Upon receiving a Click event, reset the Slider 
        /// </summary>
        private void OnClick(object sender, RoutedEventArgs e)
        {
            if(Slider != null)
            {
                Slider.Value = ResetValue;
            }
        }

        #region Dependency Property - ResetValue

        /// <summary>
        /// Stores a double that will act as the definition of 'reset' 
        /// value for the Slider associated with this Behavior. 
        /// </summary>
        public double ResetValue
        {
            get { return (double)GetValue(ResetValueProperty); }
            set { SetValue(ResetValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ResetValue.
        // This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ResetValueProperty =
            DependencyProperty.Register(
                "ResetValue", 
                typeof(double), 
                typeof(SliderResetBehavior), 
                new PropertyMetadata(0.0));

        #endregion

        /// <summary>
        /// Maintains a System.Windows.Controls.Slider object upon 
        /// which this Behavior will act upon. 
        /// </summary>
        #region Dependency Property - Slider 
        public Slider Slider
        {
            get { return (Slider)GetValue(SliderProperty); }
            set { SetValue(SliderProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Slider.
        // This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SliderProperty =
            DependencyProperty.Register(
                "Slider", 
                typeof(Slider), 
                typeof(SliderResetBehavior), 
                new PropertyMetadata(default(Slider)));

        #endregion
    }

XAML 现在可以非常简单,如下所示:

<StackPanel>
    <Slider x:Name="Slider" Minimum="-5" Maximum="5" Value="{Binding SliderValue}"/>
    <!-- 
       Requires xmlns entries like this:
        xmlns:local="clr-namespace:<Namespace containing SliderResetBehavior>"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    -->
    <Button x:Name="button" Content="Reset">
        <i:Interaction.Behaviors>
            <local:SliderResetBehavior ResetValue="0" Slider="{Binding ElementName=Slider}"/>
        </i:Interaction.Behaviors>
    </Button>
</StackPanel>

上面显示的此 XAML 保留了设计人员仅使用松散 XAML 完全可自定义的关键属性,并且还允许 Slider.Value 的数据绑定。

【讨论】:

  • 如您所见,这不适用于绑定。通过绑定,它具有我在从我的非解决方案中删除 FillBehavior 时注意到的相同“锁定”效果。
  • 我能说的最好的,如果 Slider.Value 绑定到另一个值,我认为你不能这样做。动画无济于事 - 当 FillBehavior 为 Stop 时,动画将在 TimeLine 完成时恢复该值。当 FillBehavior 为 HoldEnd(默认)时,动画将继续保持结束值并防止来自其他来源的更改(如手动滑动滑块)。这就是为什么您会看到滑块在 0 时表现得好像“冻结”了一样。
  • 我很喜欢你关于使用行为的建议。我相信如果需要,作者可以通过自定义 UI 来提供行为,而不是需要对现有对象/代码的命令。我已经对你的帖子投了赞成票,但我不认为这是一个答案,因为这个问题似乎无法回答。我对在 Stack Overflow 上发帖还很陌生。也许我应该将原始问题更改为“可以....在 xaml 中完成,同时还绑定属性”在这种情况下,答案似乎是,不。
猜你喜欢
  • 2023-03-06
  • 2013-01-22
  • 1970-01-01
  • 1970-01-01
  • 2011-09-01
  • 2023-03-26
  • 2016-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多