none
WPF MVVM форма с сообщением RRS feed

  • Вопрос

  • Здравствуйте! Хочу для уведомления пользователя о чем-либо использовать вместо MessageBox своё подобие. Примерно так хочу:

    Т.е. у уведомления должно быть: соответствующая картинка (если ошибка - одна картинка, если все ок - другая), статус (заголовок - Успех, Ошибка, Предупреждение и тп), текст (об ошибке, о завершении операции и т.п.), кнопка (если все прошло ОК - появляется кнопка ОК, если ошибка - подгружаются допустим 2 кнопки - "скопировать текст ошибки" и "Выход"). Также сообщение должно уметь всплывать и в центре главной View (родительской) и в любом углу монитора. По сути это Popup.

    Что-то похожее на MessageBox.Show() надо, только со своим методом и передаваемыми значениями И, конечно же, иная визуализация (стиль, динамический контент). 

    Подскажите в какую сторону копать. Это UserControl, ContentCOntrol, ContentPresenter + DataTemplate, View, класс унаследованный от MessageBox или что использовать? 

    Я пробовал использовать UserControl (создал в нем примерное содержимое):

    <UserControl>
    
    <TextBlock Text="..."/>
    
    <Button/>
    
    </UserControl>

    В главной ViewModel вызывал UserControl:

    void DoSomeOperation()
    
    {
    
    // Конец операции - уведомляем пользователя
    
    Popup msg = new Popup();
    
    msg.Child = new MyUserControlClass();
    
    msg.IsOpen = .. и .тд.
    
    }
    Сообщение появляется в верхнем левом углу экрана. Я знаю как его разместить относительно границ экрана, но не могу разместить его по центру родительской формы! Ведь ViewModel ничего не знает о View. Это первая проблема, которую пока не могу решить. Вторая проблема - это реализация правильного своего MessageBox в рамках паттерна MVVM. По сути у меня класс UserControl также должен поддерживать INotifyPropChange, в нем должны содержаться поля Status, Icon, Text, в XAML привязка к этим полям. Соответственно вызывать UserControl   new MyUserControlClass("произошла ошибка", findRes["default-icon"], "Текст ошибки"..). Но чувствую это некрасивый путь, хотя его реализовать мне проще. Правильно подтягивать какие-нибудь шаблоны, но я не знаю как. Когда то пробовал - но не получилось разобраться. Третья проблема - динамический контент (т.е. как вместо 1 кнопки, подтянуть по условию 2, а может и 3, задать для них действия). Пока не представляю как решить. Нужна помощь словом и кодом ))) 


    7 июня 2017 г. 19:32

Ответы

  • Добрый день.

    Посмотрите здесь. Там для Store приложений, но никто не мешает этот подход применить и для других типов приложений. Если появятся вопросы, то задавайте, постараюсь ответить.

    Есть еще вариант с реализацией в виде UserControl который перекрывает всю визуальную часть формы, но там надо отдельно пример писать...

    • Помечено в качестве ответа kremlinbot 8 июня 2017 г. 19:24
    Отвечающий
  • Если формы для разных типов принципиально различаются, то да, лепить для каждого свою форму. Если они выглядят одинаково, можно справиться с помощью привязок.

    Вам нужно создать класс, который будет содержать параметры сообщения - текст, тип, необходимые кнопки и т.п. - который будет передаваться в конструктор окна. Поскольку вид окна сообщения не меняется во времени, он может быть предельно простым, без NotifyPropertyChanged. Визуальные параметры окна связать со свойствами этого класса с помощью DataTrigger. А для переменного числа кнопок использовать ItemsControl.ItemTemplate

    public enum MessageBoxType
        {
            Error,Warning,Information
        }
    
        public class MessageBoxParams
        {
            public string Text { get; set; }
            public MessageBoxType Type { get; set; }
            public string[] Buttons { get; set; }
    
            public MessageBoxParams(string text,MessageBoxType type,string[] butt)
            {
                Text = text; Type = type; Buttons = butt;
            }
    
            public MessageBoxParams()
            {
                Text = ""; Type = MessageBoxType.Information; Buttons = new string[] { "OK" };
            }
        }

    Разметка окна:

    <Window x:Class="WpfTestApp1.CoolMessageBox"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Height="300" Width="399">
        <Window.Style>
            <Style TargetType="{x:Type Window}">
                <Setter Property="Title" Value="Предупреждение"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Type}" Value="Error">
                        <Setter Property="Title" Value="Ошибка"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Type}" Value="Information">
                        <Setter Property="Title" Value="Информация"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Window.Style>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="70"/>
            </Grid.RowDefinitions>
           <TextBlock  HorizontalAlignment="Stretch" Grid.Row="0" TextAlignment="Center" 
                       Name="textBlock1" Text="{Binding Path=Text}" VerticalAlignment="Stretch" >
            <TextBlock.Style>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Foreground" Value="Black"/>
                <Style.Triggers>                
                    <DataTrigger Binding="{Binding Type}" Value="Error">
                        <Setter Property="Foreground" Value="Red"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Type}" Value="Information">
                        <Setter Property="Foreground" Value="Green"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style></TextBlock>
    
            <ItemsControl Name="items" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center">
    
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel VerticalAlignment="Center" HorizontalAlignment="Center"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
    
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button Content="{Binding Path=''}" Click="button1_Click"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
    
        </Grid>
    </Window>

    Код окна:

    public partial class CoolMessageBox : Window
        {
            MessageBoxParams pars;
    
            public string Result { get; set; }
    
            public CoolMessageBox(MessageBoxParams p)
            {
                InitializeComponent();
                pars = p;
                this.DataContext = pars;
                items.ItemsSource = pars.Buttons;
            }
    
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                Result = (sender as Button).Content.ToString();
                this.Close();
            }
        }

    Использование:

    CoolMessageBox box = new CoolMessageBox(
                    new MessageBoxParams("Ваше любимое животное?",
                        MessageBoxType.Information,
                        new string[]{"Собака","Кошка"}));
                box.ShowDialog();
                label1.Content = "Пользователь выбрал: "+box.Result;
    Если среда не позволяет плодить окна - вместо Window использовать Popup + UserControl, но в остальном все также.

    • Помечено в качестве ответа kremlinbot 8 июня 2017 г. 19:23
    8 июня 2017 г. 19:12

Все ответы

  • А свое окно MVVM сделать не позволяет? Или речь идет о браузерном WPF?
  • Добрый день.

    Посмотрите здесь. Там для Store приложений, но никто не мешает этот подход применить и для других типов приложений. Если появятся вопросы, то задавайте, постараюсь ответить.

    Есть еще вариант с реализацией в виде UserControl который перекрывает всю визуальную часть формы, но там надо отдельно пример писать...

    • Помечено в качестве ответа kremlinbot 8 июня 2017 г. 19:24
    Отвечающий
  • А свое окно MVVM сделать не позволяет? Или речь идет о браузерном WPF?

    Позволяет. Если честно не понял вопрос. Если он - почему нельзя сообщение сделать как форму View, то я как раз и уточняю, что использовать лучше. 

    Алексей, спасибо, посмотрю!

  • Вопрос в том, почему бы не сделать обычное окно (Window), если это не браузерное приложение. И в каком смысле "лучше"? Отображение окна сообщения - довольно тривиальная задача, чтобы говорить о лучших практиках. Каждый делает, как ему удобнее.
  • Вадим, "лучше" означает "Использовать наиболее приспособленный (подходящий) инструмент для достижения поставленной задачи с учетом требований к решению". Например,мне надо обороняться. Есть кирпич. Им это можно сделать, но больше он подходит для строительства. Поэтому лучше выбрать пистолет. Удобно, практично, эффективно.

    Тот же пример с формами. У меня есть статусы: Ошибка, Предупреждение, Успех. Можно создать для каждого статуса свою форму и вызывать ее. Но это же не совсем верный подход, верно? Что будет если статусов станет 10? Для каждого лепить свою форму? Поэтому надо делать один шаблон, но в него подтягивать разное содержимое.

    Вот и я уточняю, каким решением можно достичь результата, чтобы избежать костылей, велосипедов, пушкой по воробьям и т.п.

    Согласен, что задача тривиальная и не самая важная в проектах. Почему не окно (View).. Да вот хз ))) Как-то сразу был нацелен на Popup+Usercontrol.  Наверно, где-то глубоко осело, что окно (View) для более серьезного контента, чем скромное уведомление.  Как раз думал, что мне тут расскажут почему надо использовать вот это, а не то :))

    8 июня 2017 г. 17:07
  • Если формы для разных типов принципиально различаются, то да, лепить для каждого свою форму. Если они выглядят одинаково, можно справиться с помощью привязок.

    Вам нужно создать класс, который будет содержать параметры сообщения - текст, тип, необходимые кнопки и т.п. - который будет передаваться в конструктор окна. Поскольку вид окна сообщения не меняется во времени, он может быть предельно простым, без NotifyPropertyChanged. Визуальные параметры окна связать со свойствами этого класса с помощью DataTrigger. А для переменного числа кнопок использовать ItemsControl.ItemTemplate

    public enum MessageBoxType
        {
            Error,Warning,Information
        }
    
        public class MessageBoxParams
        {
            public string Text { get; set; }
            public MessageBoxType Type { get; set; }
            public string[] Buttons { get; set; }
    
            public MessageBoxParams(string text,MessageBoxType type,string[] butt)
            {
                Text = text; Type = type; Buttons = butt;
            }
    
            public MessageBoxParams()
            {
                Text = ""; Type = MessageBoxType.Information; Buttons = new string[] { "OK" };
            }
        }

    Разметка окна:

    <Window x:Class="WpfTestApp1.CoolMessageBox"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Height="300" Width="399">
        <Window.Style>
            <Style TargetType="{x:Type Window}">
                <Setter Property="Title" Value="Предупреждение"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Type}" Value="Error">
                        <Setter Property="Title" Value="Ошибка"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Type}" Value="Information">
                        <Setter Property="Title" Value="Информация"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Window.Style>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="70"/>
            </Grid.RowDefinitions>
           <TextBlock  HorizontalAlignment="Stretch" Grid.Row="0" TextAlignment="Center" 
                       Name="textBlock1" Text="{Binding Path=Text}" VerticalAlignment="Stretch" >
            <TextBlock.Style>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Foreground" Value="Black"/>
                <Style.Triggers>                
                    <DataTrigger Binding="{Binding Type}" Value="Error">
                        <Setter Property="Foreground" Value="Red"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Type}" Value="Information">
                        <Setter Property="Foreground" Value="Green"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style></TextBlock>
    
            <ItemsControl Name="items" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center">
    
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel VerticalAlignment="Center" HorizontalAlignment="Center"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
    
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button Content="{Binding Path=''}" Click="button1_Click"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
    
        </Grid>
    </Window>

    Код окна:

    public partial class CoolMessageBox : Window
        {
            MessageBoxParams pars;
    
            public string Result { get; set; }
    
            public CoolMessageBox(MessageBoxParams p)
            {
                InitializeComponent();
                pars = p;
                this.DataContext = pars;
                items.ItemsSource = pars.Buttons;
            }
    
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                Result = (sender as Button).Content.ToString();
                this.Close();
            }
        }

    Использование:

    CoolMessageBox box = new CoolMessageBox(
                    new MessageBoxParams("Ваше любимое животное?",
                        MessageBoxType.Information,
                        new string[]{"Собака","Кошка"}));
                box.ShowDialog();
                label1.Content = "Пользователь выбрал: "+box.Result;
    Если среда не позволяет плодить окна - вместо Window использовать Popup + UserControl, но в остальном все также.

    • Помечено в качестве ответа kremlinbot 8 июня 2017 г. 19:23
    8 июня 2017 г. 19:12
  • Вадим, спасибо, круто!

    Думаю тема раскрыта полностью, 2 отличных разжеванных ответа! Благодарю за помощь!

    [РЕШЕНО]

    8 июня 2017 г. 19:23