如何在WPF中模仿这种行为?

时间:2018-07-27 16:03:21

标签: c# wpf user-interface

我是WPF和C#开发的新手,并且正在开发此应用程序。我不知道是否有人熟悉VOIP App Discord,但他们有我真正喜欢的特定行为,并想尝试使用WPF创建类似的样式。

在Discord上添加服务器时,单击一个按钮,整个后窗口消失,并且在前台打开一个新窗口,提示您添加服务器。在窗口外部单击会导致该窗口在背景窗口关闭并重新聚焦。

无论如何,这是一个GIF来帮助证明这种特定行为(忽略星星,不想泄露个人信息)。

https://i.imgur.com/cn0sFlO.gifv

我真的很陌生,所以不知道如何模仿这种行为。

2 个答案:

答案 0 :(得分:2)

这是一个自定义的Flyout控件,您可以使用它并对其进行操作,就像您要查找的一样。

以下内容将向您展示如何编写和使用自定义Flyout,就像这样:

<Window.Resources>
    <local:FlyoutControl x:Key="CustomFlyout">
        <Grid Width="400" Height="200" Background="PeachPuff">
            <TextBlock Text="Inside Flyout" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        </Grid>
    </local:FlyoutControl>
</Window.Resources>

<Grid>
    <Button Content="I have a flyout"
            Width="120"
            Height="40"
            local:FlyoutAttach.Flyout="{StaticResource CustomFlyout}"/>
</Grid>

我看到您已经有了一个答案,但是当我看到问题时我就开始构建它,因此,如果您想使用它并以自己的意愿使用它,那很好。自从我做出努力以来,我想我也会分享这个答案。

我建议更进一步,并添加一个AttachableProperty,该属性可用于将Flyout附加到UWP应用程序中的按钮之类的控件上。在AP中,您可以找到MainWindow并将其添加到网格的底部并自动显示。但是,这假定MainWindow具有Grid的根面板。编辑:我还添加了一个可附加属性以供参考。

FlyoutControl.xaml

<ContentControl x:Name="ContentControl" 
                x:Class="Question_Answer_WPF_App.FlyoutControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                Template="{DynamicResource ContentControlTemplate}"
                Opacity="0"
                Visibility="Hidden">

    <ContentControl.Resources>

        <Duration x:Key="OpenDuration">00:00:00.4</Duration>

        <Storyboard x:Key="OpenStoryboard" Duration="{StaticResource OpenDuration}">
            <DoubleAnimation Storyboard.TargetName="ContentControl" Storyboard.TargetProperty="Opacity" To="1" Duration="{StaticResource OpenDuration}">
                <DoubleAnimation.EasingFunction>
                    <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                </DoubleAnimation.EasingFunction>
            </DoubleAnimation>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentControl" Storyboard.TargetProperty="Visibility" Duration="{StaticResource OpenDuration}">
                <DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" KeyTime="00:00:00" />
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>

        <Storyboard x:Key="OpenInnerContentStoryboard" Duration="{StaticResource OpenDuration}">
            <DoubleAnimation Storyboard.TargetName="scaleTransform" Storyboard.TargetProperty="ScaleX" To="1" Duration="{StaticResource OpenDuration}">
                <DoubleAnimation.EasingFunction>
                    <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                </DoubleAnimation.EasingFunction>
            </DoubleAnimation>

            <DoubleAnimation Storyboard.TargetName="scaleTransform" Storyboard.TargetProperty="ScaleY" To="1" Duration="{StaticResource OpenDuration}">
                <DoubleAnimation.EasingFunction>
                    <BackEase EasingMode="EaseOut" Amplitude="0.4"/>
                </DoubleAnimation.EasingFunction>
            </DoubleAnimation>
        </Storyboard>

        <Storyboard x:Key="CloseStoryboard">
            <DoubleAnimation Storyboard.TargetName="ContentControl" Storyboard.TargetProperty="Opacity" To="0" Duration="00:00:00"/>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentControl" Storyboard.TargetProperty="Visibility">
                <DiscreteObjectKeyFrame Value="{x:Static Visibility.Hidden}" KeyTime="00:00:00" />
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>

        <Storyboard x:Key="CloseInnerContentStoryboard">
            <DoubleAnimation Storyboard.TargetName="scaleTransform" Storyboard.TargetProperty="ScaleX" To="0" Duration="00:00:00"/>
            <DoubleAnimation Storyboard.TargetName="scaleTransform" Storyboard.TargetProperty="ScaleY" To="0" Duration="00:00:00"/>
        </Storyboard>

        <ControlTemplate x:Key="InnerContentButtonTemplate" TargetType="Button">
            <ContentPresenter />
        </ControlTemplate>

        <ControlTemplate x:Key="BackgroundButtonTemplate" TargetType="Button">
            <Grid Background="Black">
                <Button VerticalAlignment="Center" HorizontalAlignment="Center" Click="InnerContentButtonClick" Template="{StaticResource InnerContentButtonTemplate}">
                    <ContentPresenter />
                </Button>
            </Grid>
        </ControlTemplate>

        <ControlTemplate x:Key="ContentControlTemplate" TargetType="ContentControl">
            <Button x:Name="BackgroundButton" Template="{StaticResource BackgroundButtonTemplate}" Background="#B2000000" Click="BackgroundButtonClick">
                <ContentPresenter RenderTransformOrigin="0.5, 0.5">
                    <ContentPresenter.RenderTransform>
                        <ScaleTransform x:Name="scaleTransform" ScaleX="0" ScaleY="0"/>
                    </ContentPresenter.RenderTransform>
                </ContentPresenter>
            </Button>
        </ControlTemplate>
    </ContentControl.Resources>

</ContentControl>

FlyoutControl.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace Question_Answer_WPF_App
{
    public partial class FlyoutControl : ContentControl
    {
        public FlyoutControl() => InitializeComponent();

        private void OpenFlyout()
        {
            var openStoryboard = Resources["OpenStoryboard"] as Storyboard;
            var openInnerContentStoryboard = Resources["OpenInnerContentStoryboard"] as Storyboard;

            openStoryboard.Begin();
            openInnerContentStoryboard.Begin(this, Template);
        }

        private void CloseFlyout()
        {
            var closeStoryboard = Resources["CloseStoryboard"] as Storyboard;
            var closeInnerContentStoryboard = Resources["CloseInnerContentStoryboard"] as Storyboard;     

            closeStoryboard.Begin();
            closeInnerContentStoryboard.Begin(this, Template);
        }

        public bool IsOpen
        {
            get { return (bool)GetValue(IsOpenProperty); }
            set { SetValue(IsOpenProperty, value); }
        }

        public static readonly DependencyProperty IsOpenProperty =
            DependencyProperty.Register(nameof(IsOpen),
                                        typeof(bool),
                                        typeof(FlyoutControl),
                                        new PropertyMetadata(false,
                                        new PropertyChangedCallback((s, e) =>
                                        {
                                            if (s is FlyoutControl flyoutControl && e.NewValue is bool boolean)
                                            {
                                                if (boolean)
                                                {
                                                    flyoutControl.OpenFlyout();
                                                }
                                                else
                                                {
                                                    flyoutControl.CloseFlyout();
                                                }
                                            }
                                        })));

        //Closes Flyout
        private void BackgroundButtonClick(object sender, RoutedEventArgs e) => IsOpen = false;

        //Disables clicks from within inner content from explicitly closing Flyout.
        private void InnerContentButtonClick(object sender, RoutedEventArgs e) => e.Handled = true;
    }
}

可以按如下方式使用:

<local:FlyoutControl>
    <MyUserControlThatLooksLikeDiscord />
</local:FlyoutControl>

您可以手动或通过绑定进行控制,例如:

隐藏代码

flyoutControl.IsOpen = !flyoutControl.IsOpen;

XAML绑定

<Grid>
    <local:FlyoutControl x:Name="flyoutControl">
        <Grid Width="400" Height="200" Background="PeachPuff">
            <TextBlock Text="Inside Flyout" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        </Grid>
    </local:FlyoutControl>

    <CheckBox IsChecked="{Binding IsOpen, ElementName=flyoutControl}" Content="Toggle Flyout" Margin="21" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>

这是可附加的属性,以便您可以更像UWP应用程序那样使用它

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace Question_Answer_WPF_App
{
    public class FlyoutAttach
    {
        public static FlyoutControl GetFlyout(ButtonBase button)
            => (FlyoutControl)button.GetValue(FlyoutProperty);

        public static void SetFlyout(ButtonBase button, FlyoutControl value)
            => button.SetValue(FlyoutProperty, value);

        public static readonly DependencyProperty FlyoutProperty =
            DependencyProperty.RegisterAttached("Flyout",
                                                typeof(FlyoutControl),
                                                typeof(FlyoutAttach),
                                                new PropertyMetadata(null,
                                                new PropertyChangedCallback((s, e) =>
                                                {
                                                    if (s is ButtonBase button && e.NewValue is FlyoutControl newFlyout)
                                                    {
                                                        if (Application.Current.MainWindow.Content is Grid grid)
                                                        {
                                                            if (e.OldValue is FlyoutControl oldFlyout)
                                                            {
                                                                grid.Children.Remove(oldFlyout);
                                                            }

                                                            grid.Children.Add(newFlyout);

                                                            button.Click -= buttonClick;
                                                            button.Click += buttonClick;
                                                        }
                                                        else
                                                        {
                                                            throw new Exception($"{nameof(Application.Current.MainWindow)} must have a root layout panel of type {nameof(Grid)} in order to use attachable Flyout.");
                                                        }

                                                        void buttonClick(object sender, RoutedEventArgs routedEventArgs)
                                                        {
                                                            newFlyout.IsOpen = true;
                                                        }
                                                    }
                                                })));

    }
}

这很简单:

<Window.Resources>
    <local:FlyoutControl x:Key="CustomFlyout" x:Name="flyoutControl">
        <Grid Width="400" Height="200" Background="PeachPuff">
            <TextBlock Text="Inside Flyout" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        </Grid>
    </local:FlyoutControl>
</Window.Resources>

<Grid>
    <Button Content="I have a flyout"
            Width="120"
            Height="40"
            local:FlyoutAttach.Flyout="{StaticResource CustomFlyout}"/>
</Grid>

窗口

enter image description here

点击的按钮

enter image description here

答案 1 :(得分:0)

在XAML中:将Window中的所有内容放入Grid中。然后,当您想要在所有内容的顶部显示“新窗口”时,只需在最后的相同Grid中添加控件即可。这将导致此新添加的控件显示在其他所有控件之上。

例如,如果您要淡出其他所有内容,请添加Canvas,其背景为黑色,不透明度为0.5。您可以添加所需的任何内容,并以所需的任何方式设计“新窗口”。

如果要查看示例代码,请查看我在GitHub上的WPF库GM.WPF。它具有Dialogs正是您要实现的目标。另外,请查看test project,以查看正在使用的对话框。