如何淡化给定控件“周围”的所有内容?

8
在我的应用程序中,某些情况下需要一些模态行为,即用户只能与UI中的特定元素交互(例如,组框内的所有控件)。我不想使用模态对话框,因此我试图找到一种方法来“淡化”除了应保持活动状态的控件之外的所有内容,最好是通过变暗其他所有内容(从而将焦点视觉集中在相关控件上)。
如何实现这样的行为?请注意,应成为模态的元素始终是UI的一部分,因此我不能只是将其放置在叠加层或类似物上。
我已经发现了装饰器和附加程序,但关于这些东西的信息非常少...
2个回答

3
你可以将一个遮罩层应用到整个窗口,并设置这个遮罩层的OpacityMask,使其在必须是模态的元素上方是透明的。我会尝试在几分钟内发布一个示例。

编辑:好的,这比我想象中的难一些...这里有一个有点丑陋但有些能用的解决方案:

    private Grid _modalOverlay;

    private void btnShowOverlay_Click(object sender, RoutedEventArgs e)
    {
        if (_modalOverlay != null)
            root.Children.Remove(_modalOverlay);
        _modalOverlay = MakeModalOverlay(groupBox1, root, 0.5);
        root.Children.Add(_modalOverlay);
    }

    private static Grid MakeModalOverlay(FrameworkElement element, FrameworkElement root, double opacity)
    {
        var offset = GetRelativeOffset(element, root);

        Grid g = new Grid();

        var c0 = new ColumnDefinition();
        c0.Width = new GridLength(offset.X);
        var c1 = new ColumnDefinition();
        c1.Width = new GridLength(element.ActualWidth);
        var c2 = new ColumnDefinition();
        c2.Width = new GridLength(root.ActualWidth - element.ActualWidth - offset.X);

        var r0 = new RowDefinition();
        r0.Height = new GridLength(offset.Y);
        var r1 = new RowDefinition();
        r1.Height = new GridLength(element.ActualHeight);
        var r2 = new RowDefinition();
        r2.Height = new GridLength(root.ActualHeight - element.ActualHeight - offset.Y);

        g.ColumnDefinitions.Add(c0);
        g.ColumnDefinitions.Add(c1);
        g.ColumnDefinitions.Add(c2);
        g.RowDefinitions.Add(r0);
        g.RowDefinitions.Add(r1);
        g.RowDefinitions.Add(r2);

        Brush b = new SolidColorBrush(Colors.Black) { Opacity = opacity };
        for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
        {
            if (i == 1 && j == 1)
                continue;

            Rectangle r = new Rectangle();
            r.Fill = b;
            Grid.SetColumn(r, i);
            Grid.SetRow(r, j);
            g.Children.Add(r);
        }

        Panel.SetZIndex(g, int.MaxValue);

        return g;
    }

    private static Vector GetRelativeOffset(Visual visual, Visual ancestor)
    {
        Visual tmp = visual;
        Vector offset = default(Vector);
        while (tmp != ancestor)
        {
            offset += VisualTreeHelper.GetOffset(tmp);

            tmp = (Visual) VisualTreeHelper.GetParent(tmp);
            if (tmp == null)
                throw new ArgumentException("ancestor is not an visual ancestor of visual");
        }
        return offset;
    }

    private void btnHideOverlay_Click(object sender, RoutedEventArgs e)
    {
        if (_modalOverlay != null)
            root.Children.Remove(_modalOverlay);
    }

在上面的代码中,root 是窗口的根面板。
这种解决方案有两个主要问题:
  • 它不支持调整大小;可能可以通过绑定覆盖网格的列宽和行高,并使用转换器来解决,但这并不是非常直接。
  • 它会阻止您点击其他控件,但您仍然可以使用键盘与它们交互。我认为防止这种情况的唯一方法是实际上将它们禁用...

处理窗口调整大小事件并在其中重新创建覆盖似乎在性能方面相当不错,而且这还没有进行任何优化...现在要找到一种简单地禁用所有控件的方法... - musefan

1

我会选择使用Adorner,因为它是WPF元素,msdn指定可以用于“视觉掩盖或覆盖部分或全部UIElement”(请参见此处)。

一个很好的起点可以在这个博客文章中找到,在那里你可能想要从渲染区域中排除你想要强调的UIElement的区域。(此时,您甚至可以创建整个窗口的VisualBrush,并用其绘制adorner,如果您想获得酷炫的效果,但否则,一种0.5不透明度的实心刷子就足够了)。

Thomas非常明智地指出,当这种“模态”行为开启时,您应该禁用所有用户不应与之交互的控件,因为只有禁用它们才能保证这些控件不以任何方式响应用户输入(或者至少使它们无法聚焦,但我认为这可能会干扰它们之前的状态,当您从“模态”状态返回时)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接