Search code examples
c#xamluwpwinui-3winui

WinUI3 - ContentDialog close on click outside of dialog


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?


Solution

  • 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.