воскресенье, 24 февраля 2013 г.

Показ MessageBox-а в Windows Store приложениях

По умолчанию, возможности показа всплывающих сообщений в Windows Store приложениях достаточно ограниченны. Открыв MSDN, мы увидим, что существует всего 2 перегрузки. Про него можно почитать здесь. Причем, среди этих перегрузок нет ни одной, которая позволяет показать хотя бы стандартную картинку. Не говоря уж о том, чтобы при удалении картинки, задавая вопрос, точно ли удаляем эту картинку, показать ее уменьшенное изображение.
Как сделать свой компонент для показа всплывающего сообщения, причем с возможностью указать картинку, мы и поговорим подкатом.

Еще раз определимся с заданием. Нам необходимо показывать всплывающее окно, которое будет содержать текст, изображение и несколько кнопок. Причем, количество кнопок может меняться. При клике пользователем на любую из кнопок. Должно происходить закрытие окна и вызов метода, который ассоциирован с кнопкой. Начнем с универсального варианта, в котором мы будем передавать текст, изображение и массив имен кнопок и обработчиков кликов на них. Для передачи в метод информации о кнопке, мы воспользуемся вот таким классом:

public class PopupButton
{
    public string Title { get; private set; }

    public Action ButtonClick { get; private set; }

    public PopupButton(string p_title, Action p_buttonClickAction)
    {
        if (string.IsNullOrEmpty(p_title))
        {
            throw new ArgumentException("Заголовок кнопки не может быть null или пустой строкой");
        }
        Title = p_title;
        ButtonClick = p_buttonClickAction;
    }
}
Для показа сообщения добавим в проект UserControl с разметкой вида:
<UserControl
    x:Class="PopupMessage.Message"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PopupMessage"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    Height="100"
    HorizontalAlignment="Stretch">   
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" MinWidth="100" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="1*" MinWidth="100" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <Rectangle Grid.RowSpan="4" Grid.ColumnSpan="4" Fill="White" Opacity="0.2" />
        <Rectangle Grid.Row="1" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="4" Fill="Black" Stroke="White" StrokeThickness="4" Margin="5,-5,5,-5" />
        <Image x:Name="imMessageImage" MaxHeight="100" Grid.Column="1" Grid.Row="1" />
        <TextBlock TextWrapping="Wrap" x:Name="tbMessage" Grid.Column="2" Grid.Row="1" Margin="7" FlowDirection="LeftToRight" />
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="2" Grid.ColumnSpan="4" x:Name="spButtons" Margin="5" />
    </Grid>
</UserControl>
Обратили внимание на два Rectangle? Второй, обводит рамкой все поле с текстом, картинкой и кнопками. А вот зачем первый, который занимает все пространство? Дело в том, что Popup, при помощи которого мы и будем показывать всплывающее окно, не блокирует компоненты которые из под него выступают. Т.е. вы показываете Popup, из под него торчат элементы управления, и пользователь имеет к ним доступ. Т.к. мы делаем аналог ShowMessage, то будем блокировать все что лежит под нашим Popup-ом посредством этого прямоугольника.
Код контрола, интересен ради свойств, которые отвечают за инициализацию:

public sealed partial class Message : UserControl
{
    public Message()
    {
        this.InitializeComponent();
    }

    public BitmapImage Bitmap
    {
        set
        {
            imMessageImage.Source = value;
        }
    }

    public string MessgaeText
    {
        set
        {
            tbMessage.Text = value;
        }
    }

    public IEnumerable<Button> Buttons
    {
        set
        {
            spButtons.Children.Clear();
            if (value != null)
            {
                foreach (var button in value)
                {
                    spButtons.Children.Add(button);
                }
            }
        }
    }
}
Ну и теперь, к самому методу. Добавляем в проект класс с именем ShowPopupMessage, а в него метод, для показа всплывающего сообщения:

public class ShowPopupMessage
{
    public static void Show(string p_message, BitmapImage p_image, params PopupButton[] p_buttons)
    {
        // Создаем Popup           
        Popup popup = new Popup();
        // Создаем кнопки
        List<Button> buttons = new List<Button>();
        if (p_buttons != null)
        {  
            foreach (var button in p_buttons)
            {
                Button createdButton = new Button() { Margin = new Thickness(5), Content = button.Title };
                createdButton.Click += (s, e) => popup.IsOpen = false;
                if (button.ButtonClick != null)
                {
                    createdButton.Click += (s, e) => button.ButtonClick();
                }
                buttons.Add(createdButton);
            }
        }
        // Инициализируем внешний вид:
        Message msg = new Message();
        msg.MessgaeText = p_message;
        msg.Bitmap = p_image;
        msg.Buttons = buttons;
        msg.Width = Window.Current.Bounds.Width;
        msg.Height = Window.Current.Bounds.Height;
        // Помещаем наш UserControl в popup
        popup.Child = msg;           
        // Показываем popup
        popup.IsOpen = true;
        // Если есть кнопки, устанавливаем фокус на первую из них
        if (buttons.Count > 0)
        {
            buttons.First().Focus(FocusState.Programmatic);
        }
    }
}
Так как все действия прокоментированны в коде, то дополниьтельно останавливаться не буду.
Для демонстрации воспользуюсь приложением, в окне которого размещу вот такой XAML:

<Page
    x:Class="PopupTester.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PopupTester"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel >
            <Button Content="Показать сообщение без картинки, с одной кнопкой" Click="Button_Click_1" />
            <TextBlock x:Name="tbResult" />
        </StackPanel>
    </Grid>
</Page>

Ну и обработчик клика на кнопку:

private void Button_Click_1(object sender, RoutedEventArgs e)
{
    ShowPopupMessage.Show(
        "Привет!",
        new BitmapImage(new Uri("http://lh5.googleusercontent.com/-xqEMlkTQlNU/AAAAAAAAAAI/AAAAAAAAA38/-AC-DOM0IIA/s512-c/photo.jpg")),
        new PopupButton("Ок!", () => tbResult.Text = "Нажали Ок"),
        new PopupButton("Отмена", () => tbResult.Text = "Нажали отмена")
        );
}
Запускаем приложение:
Кликаем на кнопку:
Закрываем окно кликом по Ок:
Конечно, предложенный компонент необходимо дорабатывать. Например, если не передали ни одной кнопки, добавлять кнопку Ок, с единственной функцией - закрыть приложение. Можно еще поиграться с дизайном (особенно со шрифтами) и т.д.
 

2 комментария:

  1. Спасибо, у вас получилось все это гораздо лучше чем у меня ;)

    ОтветитьУдалить
    Ответы
    1. Не за что. Мы просто для SilverLight уже делали всплывающее окно на основе Popup-а, все что мне оставалось, это просто идеи от туда перенести на ограничения Windows Store приложений.
      Сегодня не обещаю, но в течении недели постараюсь привести пример, как на основе этого универсального метода сделать несколько специфических...

      Удалить