Однажды мне потребовалось открыть окно из консольного приложения. Я хотел это сделать используя wpf, но информации, разбросанной по сети оказалось мало, так что я решил как-то систематизировать и представить этот маленький туториал.

Созадем обычное консольное приложение на .net framework.



Теперь необходимо добавить зависимости: WindowsBase, PresentationCore, PresentationFramework.



Добавим класс нашего окна, унаследовав его от стандартных окон винды.

 public class MyWindow : Window{}

Добавим методу main атрибут [STAThread]

Зачем
STAThreadAttribute по существу является обязательным требованием для обмена сообщениями с сервером сообщений Windows с компонентами COM
А подробнее.


[STAThread]
public static void Main(string[] args){}

Теперь создадим наше окно:

 [STAThread]
public static void Main(string[] args)
{
    var win = new MyWindow { Width = 350, Height = 350};
    var grid = new Grid();
    var text = new TextBox {Text = "my text"};
    grid.Children.Add(text);
    win.Content = grid;
}

Если мы теперь вызовем на окне метод Show(), оно тут же схлопнется, а так как нам бы хотелось на него смотреть все время, то это окно нужно запихнуть в контейнер, который поддерживает весь жизненный цикл.

app.MainWindow = win;
app.MainWindow.Show();        
app.Run();

Мы отобразили окно, и оно неплохо себя чувствует, но закрыть его из кода так просто не получится: метод Run() — являет из себя бесконечный цикл, а остановить Application можно только из того же потока, где он вызван. Выход:


 Task.Run(async  () =>
            {
                await  Task.Delay(1000);
               app.Dispatcher.Invoke((Action) delegate { app.Shutdown(); });
            });
;

Тогда весь метод выглядит
так.
[STAThread]
        public static void Main(string[] args)
        {
            var app = new Application();
            var win = new MyWindow { Width = 350, Height = 350};
            var grid = new Grid();
            var text = new TextBox {Text = "my text"};
            grid.Children.Add(text);
            win.Content = grid;
            app.MainWindow = win;
            app.MainWindow.Show();
           Task.Run(async  () =>
            {
                await  Task.Delay(1000);
               app.Dispatcher.Invoke((Action) delegate { app.Shutdown(); });
            });
            app.Run();
        }

а тут исходник


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

Для этого добавляем зависимость System.Xml.
И составляем xaml документ.
<Window
    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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:ConsoleApplication1"
    mc:Ignorable="d"
    Title="MyWindow" Height="450" Width="800">
    <Grid>
        <Label Content="Label" />
    </Grid>
</Window>


Теперь загружаем данные из файла.
XmlTextReader r = new XmlTextReader("MyWin.xaml");
var win = XamlReader.Load(r) as Window;


И в таком варианте конечный Main выглядит
так.
[STAThread]
        public static void Main(string[] args)
        {
            var app = new Application();
            XmlTextReader r = new XmlTextReader("MyWin.xaml");
            var win = XamlReader.Load(r) as Window;
            app.MainWindow = win;
            app.MainWindow.Show();
            Task.Run(async  () =>
            {
                await  Task.Delay(1000);
               app.Dispatcher.Invoke((Action) delegate { app.Shutdown(); });
            });
            app.Run();
        }



P.S.
Спасибо с# чату в тг и пользователю Юрий.

Комментарии (10)


  1. vasyan
    27.01.2019 00:55

    Интересно, то что эта статья видна в списке английских статей — это баг или фича?


    1. Larymar Автор
      27.01.2019 01:40

      спасибо
      почему-то дефолтный был англ, вроде поправил


  1. iliazeus
    27.01.2019 07:59

    Добавим методу main атрибут [STAThread]
    Зачем?

    А действительно, зачем? Из приведенной ссылки это не слишком понятно.

    Если мы теперь вызовем на окне метод Show(), оно тут же схлопнется, а так как нам бы хотелось на него смотреть все время, то это окно нужно запихнуть в контейнер, который поддерживает весь жизненный цикл.

    Почему вам не подошел Window.ShowDialog()?


    1. Larymar Автор
      27.01.2019 11:59

      он захватывает фокус, а я хотел добиться стандартного поведения окна
      Думаю, я внес уточнения и теперь «зачем» стал куда более понятным


  1. fedorro
    27.01.2019 11:02
    +1

    Окно отображается, а после закрывается т.к. выполнение кода заканчивается и окно закрывается, и,! внезапно!, нужен цикл обработки событий. (Ну или просто Thread.Sleep(10000) воткнуть если нужно только показать окно). Кроме того Ваш код нерабочий: Task.Delay(1000); без Wait ничего не ждет, а Application.Current.Shutdown(); свалится с исключением межпоточного взаимодействия — даже проверил специально. Если хотите и окно и консоль — сделайте оконное приложение и в свойствах проекта выберите тип «Консольное приложение».


    1. Larymar Автор
      27.01.2019 11:51

      Спастбо, вы правы, а я при написании поста допустил грубую ошибку по причине версионирования


  1. addewyd
    27.01.2019 13:43

    Так, наверное, было бы интереснее:

    using System.Xml;
    using System.Windows.Markup;
    
    namespace ConsoleApplication1
    {
        internal class Program
        {
            [STAThread]
            public static void Main(string[] args)
            {
                XmlTextReader r = new XmlTextReader("MyWindow.xaml");
                
                var app = new Application();            
                app.MainWindow = XamlReader.Load(r) as Window;
                app.MainWindow.Show();
    // ... etc
    

    XAML:

    <Window
            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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:ConsoleApplication1"
            mc:Ignorable="d"
            Title="MyWindow" Height="450" Width="800">
        <Grid>
            <Label Content="Label" HorizontalAlignment="Left" Margin="80,53,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5" Width="127">
            </Label>
            <Button Content="Button" HorizontalAlignment="Left" Height="40" Margin="80,105,0,0" VerticalAlignment="Top" Width="144"/>
    <!-- и т.д. по вкусу -->
        </Grid>
    </Window>
    
    


  1. dikkini
    27.01.2019 23:50

    Консольное приложение для винды? Зачем?


  1. mayorovp
    28.01.2019 10:21

    Вот так не проще ли?


    app.Dispatcher.InvokeAsync((Action)(async () => {
        await Task.Delay(1000);
        app.Shutdown();
    }));
    app.Run(win);


  1. xystarcha
    29.01.2019 20:13

    Как-то переусложнено. Мы готовим обычный WPF класс, потом

    var thread = new Thread(() =>
                       {
                            mywindow = new MyWindow();
                            mywindow.Show();
                            Dispatcher.Run();
                       });
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    ...
    var dispatcher = Dispatcher.FromThread(thread);
    dispatcher.InvokeShutdown();
    


    Никаких STA на главном потоке, никаких связей с Application.