Reputation: 59
I'm fairly new to WPF and C# Development and I'm making this application. I don't know if anyone is familiar with the VOIP App Discord, but they have a specific behavior that I really like and want to try to create a similar style with WPF.
When you add a server on Discord, you click a button and the entire back window fades and a new window opens up in the foreground prompting you to add the server. Clicking outside the window causes the window to close at the background window to come back into focus.
Anyway, Here is a GIF to help demonstrate this specific behavior (ignore the stars, didn't want to give away personal information).
https://i.imgur.com/cn0sFlO.gifv
I'm really new to all of this so no idea how I could go about mimicking this behavior.
Upvotes: 2
Views: 905
Reputation: 6749
Here's a custom Flyout control that you can use and manipulate that works like what you're looking for.
The following will show you how to write and use a custom Flyout much like this:
<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>
I see you already have an answer but I started building this when I saw the question so if you want to take it and use it how you wish that's fine; I figured I would share this answer also since I put forth the effort.
I suggest taking it one step further and adding an AttachableProperty that can be used to attach the Flyout to controls like buttons has in UWP applications. In the AP you can find the MainWindow and add it to the base of the grid and show it automatically; this however assumes the MainWindow has a root panel of Grid. Edit: I've added an attachable property for reference also.
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;
}
}
Can be used like:
<local:FlyoutControl>
<MyUserControlThatLooksLikeDiscord />
</local:FlyoutControl>
And you can control it manually or with binding like so:
Code Behind
flyoutControl.IsOpen = !flyoutControl.IsOpen;
XAML Binding
<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>
Here's the attachable property so that you can use it a bit more like UWP apps
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;
}
}
})));
}
}
And this used as easy as this:
<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>
Window
Clicked Button
Upvotes: 3
Reputation: 2857
In XAML: Put everything in your Window
into a Grid
. Then, when you want to show your "new window" on top of everything, simply add controls into that same Grid
at the end. This will cause this new added Controls to show up on top of everything else.
For example, if you want everything else to be faded out, then add Canvas
with Black Background and 0.5 Opacity. You can add anything you want and design your "new window" in any way you want.
If you want to see some example code, have a look at my WPF library GM.WPF on GitHub. It has Dialogs that are exactly what you are trying to achieve. Also take a look at the test project to see the dialogs in action.
Upvotes: 0