我有一个包含多个按钮的 Window
。我需要跟踪按下按钮的时间 (MouseDown
) - 开始操作 - 以及何时释放或离开按钮(MouseUp
或 MouseLeave
) 结束/取消操作。
取消可能需要一段时间,在此期间我需要防止用户点击另一个按钮。这是忙碌指示的经典案例。当操作结束时,我可以显示一个带有覆盖层的全局忙碌指示器。然而,在可用性方面,有更好的解决方案,但我正在努力寻找实现它的方法。
所以这就是我想要实现的目标:
1) 初始窗口状态:
2) 一旦按下按钮,窗口的其余部分应该变灰(或模糊效果)并被“禁用”。 窗口的其余部分还包括其他几个输入控件(TabControl
、带有Button
的通知 View 、ToggleButton
等. -- 所有的都需要被禁用。所以它真的是“所有的 child ,除了那个被点击的 Button
”))
3) 当按钮被释放时,操作被取消,但是因为这可能需要一段时间,所以按钮上应该显示忙碌指示(我知道怎么做这个 部分)
4) 一旦操作结束,窗口就会恢复到初始状态:
有两个重要条件:
- 在同一
Window
上还有其他相同类型(相同功能和行为)的按钮。因此,仅仅将Panel.ZIndex
设置为一个常量 是行不通的。 - 同一窗口中还有其他几个输入控件。那些也需要被“禁用”。
- 显示覆盖层/使
Window
的其余部分变灰可能不会触发鼠标事件,如MouseUp
或MouseLeave
(否则操作会立即被取消)。
研究完成
禁用窗口:我研究过使用 IsEnabled
属性。但是,默认情况下它会传播到所有子元素,您不能覆盖一个特定子元素的值。
我实际上已经找到了一种改变这种行为的方法(在 SO 上),但我担心改变这种行为可能会弄乱其他地方的东西(而且,这真的出乎意料 - future 的开发人员会把它视为魔法)。另外,我不喜欢控件在禁用状态下的样子,弄乱它会很麻烦。
所以我更喜欢使用某种“覆盖”,但它会将后面的东西变灰(我猜 color=grey, opacity=0.5
结合 IsHitTestVisible=True
将是一个开始?)。但这里的问题是我不知道如何在覆盖层顶部获得 一个 按钮,而窗口的所有其余部分都留在后面...
编辑:使用 ZIndex 似乎只适用于同一级别的项目(至少对于网格)。所以这也不是一个选择:(
最佳答案
非常有趣的问题。我试着像下面这样解决它:
使用唯一标识符在您的按钮上定义
Tag
属性(我使用了数字,但如果您将 Tag 设置为您的按钮所做的事情,这将有意义)并定义Style
如下所示:<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" xmlns:converter="clr-namespace:WpfApplication1" Tag="Win" > <Window.Resources> <converter:StateConverter x:Key="StateConverter"/> <Style TargetType="Button"> <Style.Triggers> <DataTrigger Value="False"> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource StateConverter}"> <Binding Path="ProcessStarter"/> <Binding Path="Tag" RelativeSource="{RelativeSource Self}"/> </MultiBinding> </DataTrigger.Binding> <DataTrigger.Setters> <Setter Property="Background" Value="White"/> <Setter Property="Opacity" Value="0.5"/> <Setter Property="IsEnabled" Value="False"/> </DataTrigger.Setters> </DataTrigger> </Style.Triggers> </Style> </Window.Resources> <Window.Style> <Style TargetType="Window"> <Style.Triggers> <DataTrigger Value="False"> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource StateConverter}"> <Binding Path="ProcessStarter"/> <Binding Path="Tag" RelativeSource="{RelativeSource Self}"/> </MultiBinding> </DataTrigger.Binding> <DataTrigger.Setters> <Setter Property="Background" Value="LightGray"/> </DataTrigger.Setters> </DataTrigger> </Style.Triggers> </Style> </Window.Style> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Button Tag="1" Content="1" Command="{Binding ActionCommand}" CommandParameter="{Binding Tag, RelativeSource={RelativeSource Self}}"/> <Button Tag="2" Grid.Column="1" Content="2" Command="{Binding ActionCommand}" CommandParameter="{Binding Tag, RelativeSource={RelativeSource Self}}"/> <Button Tag="3" Grid.Column="2" Content="3" Command="{Binding ActionCommand}" CommandParameter="{Binding Tag, RelativeSource={RelativeSource Self}}"/> </Grid>
然后像下面这样在您的 VM 中捕获调用 Command 的按钮的标签(这里我已经在后面的代码中定义了所有属性)
public partial class MainWindow : Window, INotifyPropertyChanged { private const string _interactiveTags = "1:2:3:Win"; private BackgroundWorker _worker; public MainWindow() { InitializeComponent(); _worker = new BackgroundWorker(); _worker.DoWork += _worker_DoWork; _worker.RunWorkerCompleted += _worker_RunWorkerCompleted; ActionCommand = new DelegateCommand(CommandHandler); DataContext = this; } private void CommandHandler(object obj) { ProcessStarter = obj.ToString(); if (!_worker.IsBusy) { _worker.RunWorkerAsync(); } } public ICommand ActionCommand { get; private set; } void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { ProcessStarter = _interactiveTags; } void _worker_DoWork(object sender, DoWorkEventArgs e) { Thread.Sleep(300); } public string _processStarter = _interactiveTags; public string ProcessStarter { get { return _processStarter; } set { _processStarter = value; RaisePropertyChanged("ProcessStarter"); } }
最后是转换器,如果这是引发命令或正在做某事的按钮,则返回
public class StateConverter : IMultiValueConverter { public string Name { get; set; } public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var tags = (values[0] as string).Split(':'); return tags.Contains(values[1] as string); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
我使用 Backgroundworker 模拟了繁重的工作,并让线程休眠了 300 毫秒。测试了它。工作正常。
关于wpf - 禁用除单击控件之外的整个窗口,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29839891/