如何通过仅在 XAML 中单击按钮来设置控件的用户可编辑 属性
How to Set a Control's user-editable property with a Button click only in XAML
具体来说,我正在尝试创建一个 "reset" 按钮,它将滑块的值设置为零。我不想要代码隐藏,因为模板可以自定义。
我尝试了以下方法。如果没有 FillBehavior="Stop"
,用户将无法再移动滑块。使用 FillBehavior="Stop"
值立即 returns 到前面的值(给出它什么都不做的外观)
<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 属性,但我也无法让它工作。它会 return 到先前的值。
请注意,滑块的值绑定到数据的 属性。
谢谢!
对您的 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% 确定)您尝试做的事情可能是不可能的。
首先,让我解释一下为什么您会看到滑块 "frozen"。这不是一个错误——我只需要仔细阅读文档以了解发生了什么。
当 FillBehavior 设置为 Stop 时,动画将在时间线完成时恢复该值(恢复到动画开始之前的值)。
另一方面,当 FillBehavior 为 HoldEnd(这是未明确指定 FillBehavior 时获得的默认行为),DoubleAnimation 将继续保持 结束值并且防止来自其他来源的更改(例如手动移动滑块)。因此,每次移动滑块时,由于 HoldEnd,Animation 都会将其重置回 0。这就是为什么您看到滑块在 0 处表现得像 "frozen"。
我调查了在 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 模板属性的优先级高于触发器和样式设置器。因此,单击 'Reset' 按钮会暂时将滑块值更改为 0,但会立即重置回相应绑定支持的原始值。
在用尽了 Animation 和 DataTriggers 之后,我得出的结论是这可能是不可能的。也就是说,考虑到 WPF 中存在的广泛功能,我很可能是错的...
编辑:12/5/2015
我想我有一个解决方案可以满足您的关键要求 - 即允许设计人员修改 UI,包括滑块的 "reset" 值的含义。也就是说,此解决方案需要代码隐藏 - 但这样的代码隐藏不会阻止 XAML 充分表达您的意图。
想法是编写一个 Behavior,并使用该 Behavior 将 Button 与 Slider 相关联,并指示此 Button 应触发 'reset' 操作。
行为的基本轮廓如下所示:
/// <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 的数据绑定。
具体来说,我正在尝试创建一个 "reset" 按钮,它将滑块的值设置为零。我不想要代码隐藏,因为模板可以自定义。
我尝试了以下方法。如果没有 FillBehavior="Stop"
,用户将无法再移动滑块。使用 FillBehavior="Stop"
值立即 returns 到前面的值(给出它什么都不做的外观)
<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 属性,但我也无法让它工作。它会 return 到先前的值。
请注意,滑块的值绑定到数据的 属性。
谢谢!
对您的 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% 确定)您尝试做的事情可能是不可能的。
首先,让我解释一下为什么您会看到滑块 "frozen"。这不是一个错误——我只需要仔细阅读文档以了解发生了什么。
当 FillBehavior 设置为 Stop 时,动画将在时间线完成时恢复该值(恢复到动画开始之前的值)。
另一方面,当 FillBehavior 为 HoldEnd(这是未明确指定 FillBehavior 时获得的默认行为),DoubleAnimation 将继续保持 结束值并且防止来自其他来源的更改(例如手动移动滑块)。因此,每次移动滑块时,由于 HoldEnd,Animation 都会将其重置回 0。这就是为什么您看到滑块在 0 处表现得像 "frozen"。
我调查了在 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 模板属性的优先级高于触发器和样式设置器。因此,单击 'Reset' 按钮会暂时将滑块值更改为 0,但会立即重置回相应绑定支持的原始值。
在用尽了 Animation 和 DataTriggers 之后,我得出的结论是这可能是不可能的。也就是说,考虑到 WPF 中存在的广泛功能,我很可能是错的...
编辑:12/5/2015
我想我有一个解决方案可以满足您的关键要求 - 即允许设计人员修改 UI,包括滑块的 "reset" 值的含义。也就是说,此解决方案需要代码隐藏 - 但这样的代码隐藏不会阻止 XAML 充分表达您的意图。
想法是编写一个 Behavior,并使用该 Behavior 将 Button 与 Slider 相关联,并指示此 Button 应触发 'reset' 操作。
行为的基本轮廓如下所示:
/// <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 的数据绑定。