Freigeben über


Using Popup to create a Dialog class

Presenting a dialog to the user is a common task for many applications. Silverlight 2 does not have a Dialog class, but it is possible to use the Popup class to create a very credible dialog. My Dialog class can't leave the Silverlight plugin--for that you would have to use Javascript interop to create a new Silverlight plugin, etc. and that's a topic for somebody else's blog :)

The key here is to make the Popup full screen (and resize it when the plugin size changes) and also have a Canvas that is full screen. Then, if you want to have a modal dialog, set the Canvas's Background property. This will prevent mouse interaction with the rest of your application. This will also enable you to detect when the user clicks outside of your dialog content, in case you want to dismiss the dialog that way.  

The one caveat that I can think of is that keyboard events can still follow the focus, which can be set to elements beneath the modal dialog. I have some ideas how to fix this in the RTM timeframe.

To use the Dialog class, you will need to subclass it, and provide an override for the GetContent method. The content that you provide will become centered (in a Grid) on the screen.

Here is the code for the Dialog class:

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Controls.Primitives;

using System.Windows.Media;

using System.Windows.Shapes;

namespace DialogApp

{

    public abstract class Dialog

    {

        public void Show(DialogStyle style)

        {

            if (_isShowing)

                throw new InvalidOperationException();

            _isShowing = true;

            EnsurePopup(style);

            _popup.IsOpen = true;

            Application.Current.Host.Content.Resized += OnPluginSizeChanged;

        }

        public void Close()

        {

            _isShowing = false;

            if (_popup != null)

            {

                _popup.IsOpen = false;

                Application.Current.Host.Content.Resized -= OnPluginSizeChanged;

            }

        }

        // Override this method to add your content to the dialog

        protected abstract FrameworkElement GetContent();

        // Override this method if you want to do something (e.g. call Close) when you click

        // outside of the content

        protected virtual void OnClickOutside() { }

        // A Grid is the child of the Popup. If it is modal, it will contain a Canvas, which

        // will be sized to fill the plugin and prevent mouse interaction with the elements

        // outside of the popup. (Keyboard interaction is still possible, but hopefully when

        // Silverlight 2 RTMs, you can disable the root to take care of that.) The Grid isn't

        // strictly needed if there is always a Canvas, but it is handy for centering the content.

        //

        // The other child of the Grid is the content of the popup. This is obtained from the

        // GetContent method.

      private void EnsurePopup(DialogStyle style)

        {

            if (_popup != null)

                return;

            _popup = new Popup();

            _grid = new Grid();

            _popup.Child = _grid;

            if (style != DialogStyle.NonModal)

            {

                // If Canvas.Background != null, you cannot click through it

                _canvas = new Canvas();

                _canvas.MouseLeftButtonDown += (sender, args) => { OnClickOutside(); };

                if (style == DialogStyle.Modal)

                {

                    _canvas.Background = new SolidColorBrush(Colors.Transparent);

                }

                else if (style == DialogStyle.ModalDimmed)

                {

                    _canvas.Background = new SolidColorBrush(Color.FromArgb(0x20, 0x80, 0x80, 0x80));

                }

                _grid.Children.Add(_canvas);

            }

            _grid.Children.Add(_content = GetContent());

            UpdateSize();

        }

        private void OnPluginSizeChanged(object sender, EventArgs e)

        {

            UpdateSize();

        }

        private void UpdateSize()

        {

            _grid.Width = Application.Current.Host.Content.ActualWidth;

            _grid.Height = Application.Current.Host.Content.ActualHeight;

            if (_canvas != null)

            {

                _canvas.Width = _grid.Width;

                _canvas.Height = _grid.Height;

            }

        }

        private bool _isShowing;

        private Popup _popup;

        private Grid _grid;

        private Canvas _canvas;

        private FrameworkElement _content;

    }

    public enum DialogStyle

    {

        NonModal,

        Modal,

        ModalDimmed

    };

}

 

Here is how I use it in my sample:

<UserControl x:Class="DialogApp.Page"

   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:local="clr-namespace:DialogApp;assembly=DialogApp">

    <Border x:Name="LayoutRoot" Background="White">

        <StackPanel Margin="8" Width="120" VerticalAlignment="Top" HorizontalAlignment="Left">

            <Button Height="24" Margin="2" Content="Show Popup" Click="Button_Click"/>

            <RadioButton Height="24" Margin="2" Content="Non-Modal" IsChecked="true"/>

            <RadioButton Height="24" Margin="2" x:Name="isModal" Content="Modal"/>

            <RadioButton Height="24" Margin="2" x:Name="isModalDimmed" Content="Modal Dimmed"/>

        </StackPanel>

    </Border>

</UserControl>

 

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Media;

namespace DialogApp

{

    public partial class Page : UserControl

    {

        public Page()

        {

            InitializeComponent();

        }

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            Dialog dlg = new MyDialog();

            if (isModal.IsChecked.Value)

                dlg.Show(DialogStyle.Modal);

            else if (isModalDimmed.IsChecked.Value)

                dlg.Show(DialogStyle.ModalDimmed);

            else

                dlg.Show(DialogStyle.NonModal);

        }

    }

    public class MyDialog : Dialog

    {

        protected override FrameworkElement GetContent()

        {

            // You could just use XamlReader to do everything except the event hookup.

            Grid grid = new Grid() { Width = 200, Height = 200, };

            Border border = new Border() { BorderBrush = new SolidColorBrush(Colors.Black), BorderThickness = new Thickness(2), CornerRadius = new CornerRadius(4), Background = new SolidColorBrush(Colors.White) };

            grid.Children.Add(border);

            grid.Children.Add(new TextBlock() { Text = "Dialog", HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Margin = new Thickness(8) });

            Button button = new Button() { Width = 50, Height = 24, Content = "Close", HorizontalAlignment = HorizontalAlignment.Right, VerticalAlignment = VerticalAlignment.Bottom, Margin = new Thickness(8) };

         grid.Children.Add(button);

            button.Click += (sender, args) => { Close(); };

            return grid;

        }

        protected override void OnClickOutside()

        {

            //Close();

        }

    }

}

 

 

 

 

DevDaveDialogApp.zip

Comments

  • Anonymous
    June 08, 2008
    You should set TabNavigation=Cycle for your user control so the user doesn't tab outside it on the modal cases. -mark

  • Anonymous
    October 01, 2008
    What if you need to design your dialogs in xaml rather than coding everything up in C# in the GetContent() method? This may be important if you have lots of complex dialogs that designers create in Blend.

  • Anonymous
    October 01, 2008
    schmiddy - In that case, you can make the dialog subclass return a new UserControl subclass for its content--and design the UserControl subclass as normal.

  • Anonymous
    October 22, 2008
    This doesn't work on 2.0 RTW if the content is non-trivial, e.g. try adding a ListBox inside the dialog and clicking on it a few times -- you get "System Exception: Catastrophic failure". I've found that doing LayoutRoot.Children.Add(_popup) sorts it, not quite sure of the implications of that though.

  • Anonymous
    October 23, 2008
    maxchristian, yes, anything that calls TransformToVisual under the hood will crash unless you do what you suggest. The implications are that the popup will be affected by the RenderTransforms that apply to the LayoutRoot, but if you are a little clever you can also get around that if need be.

  • Anonymous
    October 24, 2008
    Ah, so that's why my dialog became enormous when I made the SL control fill the browser window using a RenderTransform... Can you be more specific?  I'm not sure if I'm a little clever but I'm certainly a little lazy.  ;)

  • Anonymous
    October 24, 2008
    maxchristian, You may need to change where you put the RenderTransforms, and/or add an additional element. If you had this: <UserControl>    <Grid Name=LayoutRoot> and the RenderTransforms were on the UserControl or the Grid, and you added the Popup to the Grid, then you may need to do something like this: <UserControl>    <Grid Name=newRoot>        <Grid Name=LayoutRoot> Add the newRoot Grid, put the RenderTransforms on the LayoutRoot, and add the Popup to the newRoot.

  • Anonymous
    March 05, 2009
    How to allow to use tab and left/right arrow keys to navigate between buttons ? Currently keyboard naviation is not available. Also, zip file dowload link is broken. Andrus.

  • Anonymous
    May 25, 2009
    Thanks. This is a great class!

  • Anonymous
    June 22, 2009
    I am unable to put a ComboBox on a UserControl that is used for a popup window.  When I popup the control and touch the ComboBox it hangs.  It can be empty or not. Using SL2.  Please advise.  Thank you.

  • Anonymous
    June 23, 2009
    This is likely due to an SL2 bug. ComboBoxes in a Popup, when the Popup is not in the visual tree (i.e. in the page's XAML) don't work. This has been fixed for SL3.

  • Anonymous
    June 24, 2009
    Ok, thanks.  A work around then is to wrap your ComboBox in another UserControl in some way; it just can't sit directly on the popup control.  I tried that  and  it seems to be working. Thanks for the help.

  • Anonymous
    August 04, 2009
    Thanks! I find it useful even there is ChildWindow in Silverlight 3 now. ChildWindow is difficult to tune when it provides FullScreen button.

  • Anonymous
    August 06, 2009
    Also it should call UpdateSize() method in Show() method too because user can change the browser size while popup is closed.