powered by simpleCommunicator - 2.0.28     © 2024 Programmizd 02
Map
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / Вызов окна WPF из Delphi
10 сообщений из 10, страница 1 из 1
Вызов окна WPF из Delphi
    #40121541
Colt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Задача. Имеется приложение, написанное на Delphi (разработчик, сторонний, но контакты с ним есть). Требуется расширить функциональность приложения, добавив некоторый модуль C# (будет писаться нами). Предполагаемое взаимодействие выглядит так: пишем dll на шарпе, а в дельфийском приложении разработчики добавляют кнопку, которая будет вызывать метод из dll с нужными параметрами.
Сложность в том, что шарповый модуль во время работы должен взаимодействовать с пользователем через свои WPF-окна. Причем эти окна должны иметь «модальное» поведение по отношению к приложению (т.е. пока эти окна не закроются, управление обратно в дельфийское приложение не должно возвращаться).
Поиски в сети привели к следующему коду запуска WPF-окна:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
        [DllExport]
        public static void TestFunct([MarshalAs(UnmanagedType.BStr)] string s, [MarshalAs(UnmanagedType.BStr)] out string Result)
        {
            bool run = true;
            Thread th = new Thread(() =>
             {
                 MainWindow win = new MainWindow();
                 win.Closed += (e, o) => { s = win.txt.Text; run = false; }; // «txt» - это TextBox на окне
                 win.txt.Text = s;
                 win.Show();
                 Dispatcher.Run();
             });
            th.SetApartmentState(ApartmentState.STA);
            th.Start();
            while (run) Thread.Sleep(100);
            th.Abort();
            Result = s;
        }



И все бы хорошо (окно по дельфийской кнопке запускается, обратно, без закрытия окна, не пускает), но есть маленькая ложка дегтя: при перетаскивании окна остаются некрасивые артефакты (см. приложенный рисунок). После закрытия окна и возврата в основное приложение, они исчезают, но осадочек остается…
Через это вопрос: можно ли как-нибудь избавиться от этих артефактов, при этом не передавая управление дельфи раньше времени? А может есть более другой способ вызова модального окна WPF из дельфи?

Дополнительная информация:
  • Delphi 7
  • VS 2019
  • Целевая среда .Net Framework 4, компиляция под x86
  • Используется нугет «DllExport» v 1.7.4
Дополнительные вопросы по коду:
  • В делегате потока есть строка «Dispatcher.Run();». Если ее убрать, то в поведении (вроде бы) ничего не меняется. Она там нужна?
  • Перед выходом из метода есть строка убиения потока «th.Abort();». Если ее убрать, то разницы опять не видно. Она нужна?
...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121592
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
у вас в запускаемом методе бесконечный цикл, в котором дельфя ожидает закрытия окна, потому и не работает.
...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121597
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Colt,

Ну, во-первых, я бы не рекомендовал вызывать CLR через статические экспорты. Лучше использовать COM и взаимодействовать через COM-интерфейсы и COM-маршаллинг. Самый главный плюс такого подхода - на стороне неуправляемого кода можно через COM-интерфейс держать указатель на managed-объект с его состоянием.

Хитрого тут ничего нет, просто в сборке объявляем public-интерфейс с COMVisible=True, реализуем его в public-классе c public-конструктором без параметров. Сборку и класс в системе регистрировать не нужно. Далее в дельфи подключаем юнит JclDotNet из библиотеки JCL, инстанциируем CLR-хост, создаем домен, грузим в него сборку, и создаем экземпляр класса, полученный Variant приводим к интерфейсу, и работаем через него (перед этим библиотеку типов дотнетовской dll нужно импортировать в дельфийский юнит, стандартная операция чеhез Component->Import Component).

Во-вторых, как показать диалоговое окно, модальное по отношению к нативному окну - здесь стандартно используется WindowInteropHelper:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
static class DialogHelper
{
  internal static int ShowWindow(Window window, IntPtr ownerHandle)
  {
    var helper = new WindowInteropHelper(window) {Owner = ownerHandle};
    if (window.WindowStartupLocation == WindowStartupLocation.CenterOwner)
      window.SourceInitialized += delegate
      {
        if (!NativeMethods.GetWindowRect(ownerHandle, out var lpRect)) return;
        var target = HwndSource.FromHwnd(helper.Handle)?.CompositionTarget;
        if (target == null) return;
        var point = target.TransformToDevice.Transform(new Point(window.ActualWidth, window.ActualHeight));
        var rECT = NativeMethods.CenterRectOnSingleMonitor(lpRect, (int)point.X, (int)point.Y);
        var point2 = target.TransformFromDevice.Transform(new Point(rECT.Left, rECT.Top));
        window.WindowStartupLocation = WindowStartupLocation.Manual;
        window.Left = point2.X;
        window.Top = point2.Y;
      };
    var flag = window.ShowDialog();
    return flag.HasValue ? (flag.Value ? 1 : 2) : 0;
  }
}

static class NativeMethods
{
  public struct RECT
  {
    public int Left;

    public int Top;

    public int Right;

    public int Bottom;

    public Point Position => new Point(Left, Top);

    public Size Size => new Size(Width, Height);

    public int Height
    {
      get => Bottom - Top;
      set => Bottom = Top + value;
    }

    public int Width
    {
      get => Right - Left;
      set => Right = Left + value;
    }

    public RECT(int left, int top, int right, int bottom)
    {
      Left = left;
      Top = top;
      Right = right;
      Bottom = bottom;
    }

    public RECT(Rectangle r)
    {
      Left = r.Left;
      Top = r.Top;
      Right = r.Right;
      Bottom = r.Bottom;
    }

    public RECT(Rect rect)
    {
      Left = (int)rect.Left;
      Top = (int)rect.Top;
      Right = (int)rect.Right;
      Bottom = (int)rect.Bottom;
    }

    public void Offset(int dx, int dy)
    {
      Left += dx;
      Right += dx;
      Top += dy;
      Bottom += dy;
    }
  }
  public struct MONITORINFO
  {
    public uint cbSize;

    public RECT rcMonitor;

    public RECT rcWork;

    public uint dwFlags;
  }

  static void FindMaximumSingleMonitorRectangle(RECT windowRect, out RECT screenSubRect, out RECT monitorRect)
  {
    var rects = new List<RECT>();
    EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, delegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT rect, IntPtr lpData)
    {
      var monitorInfo = default(MONITORINFO);
      monitorInfo.cbSize = (uint)Marshal.SizeOf(typeof(MONITORINFO));
      GetMonitorInfo(hMonitor, ref monitorInfo);
      rects.Add(monitorInfo.rcWork);
      return true;
    }, IntPtr.Zero);
    var num = 0L;
    var rECT = new RECT
    {
      Left = 0,
      Right = 0,
      Top = 0,
      Bottom = 0
    };
    screenSubRect = rECT;
    rECT = new RECT
    {
      Left = 0,
      Right = 0,
      Top = 0,
      Bottom = 0
    };
    monitorRect = rECT;
    foreach (var item in rects)
    {
      var lprcSrc = item;
      IntersectRect(out var lprcDst, ref lprcSrc, ref windowRect);
      long num2 = lprcDst.Width * lprcDst.Height;
      if (num2 > num)
      {
        screenSubRect = lprcDst;
        monitorRect = item;
        num = num2;
      }
    }
  }
  static RECT CenterInRect(RECT parentRect, int childWidth, int childHeight, RECT monitorClippingRect)
  {
    var result = default(RECT);
    result.Left = parentRect.Left + (parentRect.Width - childWidth) / 2;
    result.Top = parentRect.Top + (parentRect.Height - childHeight) / 2;
    result.Width = childWidth;
    result.Height = childHeight;
    result.Left = Math.Min(result.Right, monitorClippingRect.Right) - result.Width;
    result.Top = Math.Min(result.Bottom, monitorClippingRect.Bottom) - result.Height;
    result.Left = Math.Max(result.Left, monitorClippingRect.Left);
    result.Top = Math.Max(result.Top, monitorClippingRect.Top);
    return result;
  }
  public static RECT CenterRectOnSingleMonitor(RECT parentRect, int childWidth, int childHeight)
  {
    FindMaximumSingleMonitorRectangle(parentRect, out var screenSubRect, out var monitorRect);
    return CenterInRect(screenSubRect, childWidth, childHeight, monitorRect);
  }
  [return: MarshalAs(UnmanagedType.Bool)]
  internal delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);

  [DllImport("user32.dll")]
  [return: MarshalAs(UnmanagedType.Bool)]
  internal static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData);

  [DllImport("user32.dll")]
  [return: MarshalAs(UnmanagedType.Bool)]
  internal static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO monitorInfo);

  [DllImport("user32.dll")]
  [return: MarshalAs(UnmanagedType.Bool)]
  internal static extern bool IntersectRect(out RECT lprcDst, [In] ref RECT lprcSrc1, [In] ref RECT lprcSrc2);
  
  [DllImport("user32.dll")]
  [return: MarshalAs(UnmanagedType.Bool)]
  public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
}

...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121616
Colt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Roman Mejtes, в том-то и неувязка, что дельфя ничего не может сделать пока я работаю со своим окном (и это меня устраивает), но обратная сторона палки, что дельфя в это время и свое окно отрисовать нормально не может (и это расстраивает).

Сон Веры Павловны, спасибо за указание более другого пути, сейчас буду пробовать.
...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121632
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Colt,

нужно заблокировать окно и вернуть управление.
ну и ты вызываешь Show, а не ShowDialog, не верен, что это блокирует основное окно, но вообще стоит попробовать
...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121654
Colt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
И ведь вот что интересно, вот такой примитивный код отрабатывает прекрасно:

Код: c#
1.
2.
3.
4.
5.
6.
7.
        [DllExport]
        public static void TestFunct()
        {
            //var win = new System.Windows.Window();
            var win = new System.Windows.Forms.Form();
            win.ShowDialog();
        }



А вот, если брать такой:

Код: c#
1.
2.
3.
4.
5.
6.
7.
        [DllExport]
        public static void TestFunct()
        {
            var win = new System.Windows.Window();
            //var win = new System.Windows.Forms.Form();
            win.ShowDialog();
        }



То вываливается ошибка (см. рисунок) на вызове ShowDialog
Может (в отличии от WinForm) WPF надо как-то предварительно проинициализировать?
...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121659
Colt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
... в дополнение к предыдущему. Я потому и начал заморачиваться с дополнительными потоками, что в них WPF-окошко создается и работает (просто не совсем красиво), а напрямую - нет.
Но, может я перемудрил, и надо просто как-то объяснить WPF, что можно работать и в основном потоке? WinForms же как-то смог.
...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121681
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Colt
Может (в отличии от WinForm) WPF надо как-то предварительно проинициализировать?

https://www.sql.ru/forum/1328336/flt-invalid-operation-c0000090-pri-vyzove-double-isnan-double-nan-v-winxp
У меня с учетом изложенного по ссылке (перед вызовом операции сохраняем и переопределяем регистры FPU, после операции восстанавливаем) всё работает в основном потоке, и вполне нормально. И да, показ окна - это не единственный вариант возникновения такой ошибки, в общем случае она без правильного выставления регистров может вылететь где и на чём угодно.
...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121690
Colt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Сон Веры Павловны, спасибо!
Кажется это оно и есть
...
Рейтинг: 0 / 0
Вызов окна WPF из Delphi
    #40121824
Colt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Вроде все срослось. Большое спасибо тем, кто направлял на путь истинный. Примерный итог выглядит так:

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
    public class Work
    {
        [DllExport]
        public static void TestFunct([MarshalAs(UnmanagedType.BStr)] string s, [MarshalAs(UnmanagedType.BStr)] out string Result)
        {
            int SaveFPU = _controlfp(0, 0);
            _controlfp(_CW_DEFAULT, 0xfffff);
#if DEBUG
            Thread.Sleep(1000);
#endif
            var win = new Window() { Width = 150, Height = 100, WindowStartupLocation = WindowStartupLocation.CenterScreen };
            var txt = new TextBox();
            win.Content = txt;
            txt.Text = s;
            win.ShowDialog();
            Result = txt.Text;
            _controlfp(SaveFPU, 0xfffff);
        }

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern int _controlfp(int newControl, int mask);

        const int _RC_NEAR = 0x00000000;
        const int _PC_53 = 0x00010000;
        const int _EM_INVALID = 0x00000010;
        const int _EM_UNDERFLOW = 0x00000002;
        const int _EM_ZERODIVIDE = 0x00000008;
        const int _EM_OVERFLOW = 0x00000004;
        const int _EM_INEXACT = 0x00000001;
        const int _EM_DENORMAL = 0x00080000;
        const int _CW_DEFAULT = _RC_NEAR + _PC_53 + _EM_INVALID + _EM_ZERODIVIDE + _EM_OVERFLOW + _EM_UNDERFLOW + _EM_INEXACT + _EM_DENORMAL;
    }



Примечание: Видимо в режиме отладки студия как-то хитро работает с потоками. Через это, если не делать некую (эмпирической длинны) задержку, то флаги на сопроцессор не успевают прописаться. В "боевом" режиме такого замечено не было.
...
Рейтинг: 0 / 0
10 сообщений из 10, страница 1 из 1
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / Вызов окна WPF из Delphi
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]