пятница, 14 ноября 2014 г.

Взаимодействие двух окон между собой

Т.к. на форумах MSDN буквально за последние 24 часа задают практически один и тот же вопрос дважды (тык и тык), то давайте я напишу небольшой пример. Под катом первая форма, создаст вторую форму и будет управлять ей и узнавать о событиях там происходящих. Пример простой, но надеюсь наглядный.

1. В пустой WPF проект добавляем второе окно. Даем ему имя SecondWindow. На этом окне будет компонент для ввода данных и кнопка отправить. Если то что ввели является числом, то по нажатию на кнопку "Отправить" текст будет зеленеть, если нет, то краснеть. Для этого на форму размещаем следующую разметку:

<Window x:Class="WpfApplication6.SecondWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SecondWindow" SizeToContent="WidthAndHeight">
    <StackPanel Orientation="Horizontal">
        <TextBox x:Name="tbInput" Width="150" Margin="5" />
        <Button Content="Отправить" Margin="5" Click="Button_Click" />
    </StackPanel>
</Window>

2. В коде страницы объявляем событие, через которое мы будем отправлять уведомление о нажатии кнопки и подготовленном тексте в главную форму:

public event Action<string> TextEntered;
 
protected void OnTextEntered(string p_text)
{
    var handler = TextEntered;
    if (handler != null)
    {
        handler(p_text);
    }
}

3. В эту же форму добавляем метод раскраски текста:
 
public void ConfirmNumber(bool p_isConfirm)
{
    if (p_isConfirm)
    {
        tbInput.Foreground = new SolidColorBrush(Colors.Green);
    }
    else
    {
        tbInput.Foreground = new SolidColorBrush(Colors.Red);
    }
}
 
4. Ну и обработчик клика на кнопке:
 
private void Button_Click(object sender, RoutedEventArgs e)
{
    OnTextEntered(tbInput.Text);
}
 
На этом со второй формой все. Как видно, у нее нет никакой логики для принятия решения о покраске текста. Только уведомление об окончании ввода и раскраска в зависимости от переданного параметра. Всю логику мы соберем на главной форме. XAML на ней править не буду, только код.
5. В код главной формы добавляем поле для хранения ссылки на вторую форму:

private SecondWindow _second;

6. Добавляем метод принимающий решение о введенном на второй форме тексте и уведомляющий ее о принятом решении:
 
private void CheckText(string p_text)
{
    int x;
    var result = false;
    if (int.TryParse(p_text, out x))
    {
        result = true;
    }
    _second.ConfirmNumber(result);
}
 
7. Ну и правим конструктор формы и пишем метод обслуживающий событие Loaded, для создания второго окна и подписывания предыдущего метода на событие второй формы.

public MainWindow()
{
    InitializeComponent();
    Loaded += MainWindow_Loaded;
}
 
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    _second = new SecondWindow();
    _second.TextEntered += CheckText;
    _second.Show();
}

8. Запускаем, главная форма сразу после запуска показывает вторую и вводя в ней разные значения мы можем нажимая кнопку Отправить получать разный цвет текста:
Все, главная форма может влиять на дочернюю, а дочерняя передавать по своей инициативе данные главной для обработки.

7 комментариев:

  1. И спасибо за Ваше внимание и ответ, а-то я было разочаровался в DevNetwork

    ОтветитьУдалить
    Ответы
    1. Ни очаровываться, ни разочаровываться смысла нет... Отвечающих мало, но когда у них есть возможность помочь, помогают.

      Удалить
    2. Смысл в том, что Microsoft разработала C#, разработала Visual Studio, и начинающему программисту кажется, что у разработчика будет наилучшая поддержка своих продуктов (не только MSDN), платформа для обсуждений/советов, bestpractice.

      Удалить
    3. Ну да, только вот все то что вы рассказываете очень хорошо работает на английском языке. Пока русскоговорящих покупателей не будет примерно столько-же, сколько и англоговорящих, русский MSDN будет переводиться по остаточному принципу, ответы на форуме будут только парой-тройкой энтузиастов комьюнити...

      Удалить
    4. Подключусь к комьюнити :)

      Удалить
  2. Я реализовал немного по другому(коллеги подсказали)
    У второго окна создал свойство
    public MainWindow MainWindow { get; set; }
    В MainWindow
    Создаю тред
    Thread NotifyThread = new Thread(() => {
    NotificationWindow Toast = new NotificationWindow();
    Toast.Show();
    Toast.MainWindow = this;
    Toast.Closed += (sender2, e2) => { Toast.Dispatcher.InvokeShutdown(); };
    System.Windows.Threading.Dispatcher.Run();
    }
    );
    NotifyThread.SetApartmentState(ApartmentState.STA);
    NotifyThread.Start();
    Теперь во втором окне
    toasty.MouseLeftButtonDown += (obj, arg) => this.MainWindow.ShowToast(((System.Windows.Controls.TextBlock)obj).Text);
    Отрабатывается метод ShowToast главного окна
    public void ShowToast(String text) {
    this.Dispatcher.Invoke((Action)(() =>
    {
    tick.Text = text;
    }));
    }
    Единственное, у меня теперь такая задачка, т.к. главное окно Maximized,то как создать второе окно, чтобы не показывался taskbar? Как-то на фоне fullscreen приложения появляющийся taskbar портит целостное впечатление

    ОтветитьУдалить
    Ответы
    1. У вашего подхода есть небольшое нарушение парадигм ООП. У вас главное окно имеет ссылку на всплывающее, а всплывающее на главное. Т.е. если вы захотите готовое всплывающее окно перенести в другой проект, а главная форма будет там с другими полями/методами, то вам придется код менять. В предложенном мной подходе, дочернее окно ничего не знает о главном и может быть вынесено в отдельную сборку и без переделываний использоваться в других проектах, где, например, текст будет проверяться не на приведение к числу, а на количество слов или еще что-нибудь

      Удалить