I migrate my UWP app to WinUI3 and I have problem with my custom implementation of ContentDialog where I have in UWP implementation which allow close the dialog when user click outside of dialog (the blank area).
Here is my implementation from UWP:
public sealed partial class CustomContentDialog : ContentDialog, INotifyPropertyChanged
{
private string _titleText = string.Empty;
private string _messageText = string.Empty;
private string _buttonText = string.Empty;
private DispatcherTimer _timer = null;
private int _timerInterval = 0;
private IMvxCommand _actionCommand = null;
public bool isHide = true;
public CustomContentDialog()
{
InitializeComponent();
InitializeObserving();
InitializeView();
}
public CustomContentDialog(string titleText, string messageText, string buttonText, IMvxCommand actionCommand = null)
{
_titleText = titleText;
_messageText = messageText;
_buttonText = buttonText;
_actionCommand = actionCommand;
InitializeComponent();
InitializeObserving();
InitializeView();
}
public CustomContentDialog(string titleText, string messageText, string buttonText, int timerInterval, IMvxCommand actionCommand = null)
{
_titleText = titleText;
_messageText = messageText;
_buttonText = buttonText;
_timerInterval = timerInterval;
_actionCommand = actionCommand;
InitializeComponent();
InitializeObserving();
InitializeView();
}
private void InitializeObserving()
{
Opened += (s, e) =>
{
isHide = false;
Microsoft.UI.Xaml.Window.Current.CoreWindow.PointerPressed += CoreWindow_PointerPressed;
};
Closed += (s, e) =>
{
isHide = true;
Microsoft.UI.Xaml.Window.Current.CoreWindow.PointerPressed -= CoreWindow_PointerPressed;
};
}
private void CoreWindow_PointerPressed(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.PointerEventArgs args)
{
if (!isHide)
{
if (_actionCommand != null)
_actionCommand.Execute(null);
else
Hide();
}
}
private void InitializeView()
{
Title.Text = _titleText;
Message.Text = _messageText;
Button.Content = _buttonText;
ProgressBar.Visibility = Visibility.Collapsed;
if (_timerInterval > 0)
{
ProgressBar.Visibility = Visibility.Visible;
ProgressBar.Maximum = _timerInterval;
_timer = new DispatcherTimer()
{
Interval = TimeSpan.FromSeconds(0.5)
};
_timer.Tick += TimerTick;
_timer.Start();
}
if (_actionCommand == null)
{
Button.Click += delegate
{
Hide();
};
}
else
{
Button.Click += delegate
{
_actionCommand.Execute(null);
};
}
}
private void TimerTick(object sender, object e)
{
if (TimerValue <= _timerInterval)
{
TimerValue += 0.5;
}
else
{
_timer.Stop();
_timer.Tick -= TimerTick;
}
}
private double _timerValue = 1;
public double TimerValue
{
get => _timerValue;
set
{
_timerValue = value;
NotifyPropertyChanged(nameof(TimerValue));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml:
<ContentDialog
x:Class="MyApp.WinUI3.Controls.CustomContentDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:MyApp.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{StaticResource DialogBackgroundBrush}"
BorderBrush="{StaticResource DialogBorderBrush}"
CornerRadius="15"
Foreground="{StaticResource DialogForegroundBrush}"
mc:Ignorable="d">
<ContentDialog.Content>
<StackPanel>
<TextBlock
Name="Title"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{StaticResource GothamBold}"
FontSize="{StaticResource DialogTitleFontSize}"
FontWeight="Bold"
Text="Title" />
<TextBlock
Name="Message"
Margin="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{StaticResource GothamBook}"
FontSize="{StaticResource DialogMessageFontSize}"
FontWeight="Normal"
Text="Message"
TextAlignment="Center"
TextTrimming="WordEllipsis"
TextWrapping="Wrap" />
<ProgressBar
Name="ProgressBar"
Height="10"
Margin="10"
CornerRadius="5"
Foreground="{StaticResource PrimaryBrush}"
IsEnabled="True"
Maximum="0"
Visibility="Collapsed"
Value="{x:Bind TimerValue, Mode=TwoWay}" />
<StackPanel HorizontalAlignment="Center" Orientation="Vertical">
<Button
Name="Button"
Width="150"
Height="50"
Margin="10"
Background="{StaticResource DialogButtonBackgroudBrush}"
Command="{Binding Action}"
Content="Ok"
FontFamily="{StaticResource GothamBold}"
FontSize="{StaticResource DialogButtonFontSize}"
FontWeight="Bold"
Foreground="{StaticResource DialogButtonForegroundBrush}"
Style="{StaticResource PrimaryHalfRoundedButtonStyle}" />
</StackPanel>
</StackPanel>
</ContentDialog.Content>
</ContentDialog>
Problem in WinUI3 is Microsoft.UI.Xaml.Window.Current.CoreWindow is still null so this implementation doesn't work. I tried something different but still nothing works now. So is there any way to achieve this in WinUI3?
Since it seems that it's a bit difficult to do this with the ContentDialog
, let me show you another way that should be something close to what you are looking for:
CustomDialog.cs
using Microsoft.UI.Xaml;
using System.Threading.Tasks;
using WinUIEx;
namespace App1;
public enum CustomDialogResult
{
Cancel,
OK,
}
public partial class CustomDialog : WindowEx
{
private TaskCompletionSource<CustomDialogResult>? _taskCompletionSource;
public CustomDialog()
{
this.InitializeComponent();
this.IsAlwaysOnTop = true;
this.CenterOnScreen();
this.SetWindowStyle(WindowStyle.Caption);
}
public new object? Content { get; set; }
public Task<CustomDialogResult> ShowAsync()
{
this.ContentPresenter.Content = Content;
_taskCompletionSource = new();
this.Activate();
return _taskCompletionSource.Task;
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
_taskCompletionSource?.SetResult(CustomDialogResult.OK);
this.Close();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
_taskCompletionSource?.SetResult(CustomDialogResult.Cancel);
this.Close();
}
}
CustomDialog.xaml.cs
<ex:WindowEx
x:Class="App1.CustomDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ex="using:WinUIEx"
xmlns:local="using:App1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Width="300"
Height="200"
mc:Ignorable="d">
<Grid
RowDefinitions="*,Auto">
<ContentPresenter
x:Name="ContentPresenter"
Grid.Row="0" />
<Grid
Grid.Row="1"
ColumnDefinitions="*,*">
<Button
Grid.Column="0"
Click="OKButton_Click"
Content="OK" />
<Button
Grid.Column="1"
Click="CancelButton_Click"
Content="Cancel" />
</Grid>
</Grid>
</ex:WindowEx>
and use it like this:
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
private CustomDialog? CustomDialog { get; set; }
private async void Button_Click(object sender, RoutedEventArgs e)
{
CustomDialog = new()
{
Title = "Title",
Content = "Are you ready?",
};
this.RootGrid.PointerPressed += RootGrid_PointerPressed;
CustomDialogResult result = await CustomDialog.ShowAsync();
this.RootGrid.PointerPressed -= RootGrid_PointerPressed;
(sender as Button)!.Content = result.ToString();
}
private void RootGrid_PointerPressed(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
CustomDialog?.Close();
}
}
The code above should have some missing features, but I hope it gives you some direction.
NOTE:
I'm using the WinUIEx NuGet package here but you should be able to do this without it.