Перенаправление отрисовки Direct2D в элемент управления WPF
Я разрабатываю приложение для рисования в Visual C++
С помощью Direct2D
.
У меня есть демо-приложение, где:
// create the ID2D1Factory
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
// create the main window
HWND m_hwnd = CreateWindow(...);
// set the render target of type ID2D1HwndRenderTarget
m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&m_pRenderTarget
);
и когда я получаю WM_PAINT
сообщение Я рисую свои фигуры.
теперь мне нужно разработать элемент управления WPF (своего рода панель), который представляет мою новую цель рендеринга (поэтому он заменит главное окно m_hwnd
), так что я могу создать новый (C#) проект WPF с главным окном, которое имеет в качестве дочерних элементов мою пользовательскую панель, в то время как часть рендеринга остается в собственном проекте DLL C++/CLI.
как я могу это сделать? Что я должен установить в качестве новой цели рендеринга?
Я думал использовать дескриптор моего окна WPF:
IntPtr windowHandle = new WindowInteropHelper(MyMainWindow).Handle;
но мне нужно рисовать на моей панели, а не на моем окне.
обратите внимание, что я не хочу использовать классы WPF для части рендеринга (Shapes
, DrawingVisuals
...)
4 ответов
вы должны реализовать класс, в котором размещается окно Win32 m_hwnd
. Этот класс наследуется от HwndHost.
кроме того, вы должны переопределить HwndHost.BuildWindowCore
и HwndHost.DestroyWindowCore
методы:
HandleRef BuildWindowCore(HandleRef hwndParent)
{
HWND parent = reinterpret_cast<HWND>(hwndParent.Handle.ToPointer());
// here create your Window and set in the CreateWindow function
// its parent by passing the parent variable defined above
m_hwnd = CreateWindow(...,
parent,
...);
return HandleRef(this, IntPtr(m_hwnd));
}
void DestroyWindowCore(HandleRef hwnd)
{
DestroyWindow(m_hwnd); // hwnd.Handle
}
пожалуйста, следуйте пошаговой инструкции: Пошаговое Руководство: размещение элемента управления Win32 в WPF.
я постараюсь ответить на вопрос, как можно лучше, на основе WPF 4.5 развязал Главу 19. Если вы хотите посмотреть его, вы можете найти всю информацию там в подразделе "смешивание содержимого DirectX с содержимым WPF".
ваша DLL C++ должна иметь 3 открытых метода Initialize (), Cleanup () и Render (). Интересными методами являются Initialize () и InitD3D (), который вызывается Initialize ():
extern "C" __declspec(dllexport) IDirect3DSurface9* WINAPI Initialize(HWND hwnd, int width, int height)
{
// Initialize Direct3D
if( SUCCEEDED( InitD3D( hwnd ) ) )
{
// Create the scene geometry
if( SUCCEEDED( InitGeometry() ) )
{
if (FAILED(g_pd3dDevice->CreateRenderTarget(width, height,
D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0,
true, // lockable (true for compatibility with Windows XP. False is preferred for Windows Vista or later)
&g_pd3dSurface, NULL)))
{
MessageBox(NULL, L"NULL!", L"Missing File", 0);
return NULL;
}
g_pd3dDevice->SetRenderTarget(0, g_pd3dSurface);
}
}
return g_pd3dSurface;
}
HRESULT InitD3D( HWND hWnd )
{
// For Windows Vista or later, this would be better if it used Direct3DCreate9Ex:
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
// Set up the structure used to create the D3DDevice. Since we are now
// using more complex geometry, we will create a device with a zbuffer.
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
// Create the D3DDevice
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
// Turn on the zbuffer
g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
// Turn on ambient lighting
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff );
return S_OK;
}
давайте перейдем к XAML код:
xmlns:interop="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
<Button.Background>
<ImageBrush>
<ImageBrush.ImageSource>
<interop:D3DImage x:Name="d3dImage" />
</ImageBrush.ImageSource>
</ImageBrush>
</Button.Background>
я установил его в качестве фона кнопки здесь, используя ImageBrush. Я считаю, что добавление его в качестве фона-хороший способ отображения содержимого DirectX. Тем не менее, вы можете использовать изображение любым способом.
для инициализации рендеринга получите дескриптор текущего окна и вызовите с ним метод Initialize() библиотеки DLL:
private void initialize()
{
IntPtr surface = DLL.Initialize(new WindowInteropHelper(this).Handle,
(int)button.ActualWidth, (int)button.ActualHeight);
if (surface != IntPtr.Zero)
{
d3dImage.Lock();
d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface);
d3dImage.Unlock();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
}
CompositionTarget.Событие рендеринга запускается непосредственно перед отображением пользовательского интерфейса. Вы должны сделать свой Содержимое DirectX здесь:
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (d3dImage.IsFrontBufferAvailable)
{
d3dImage.Lock();
DLL.Render();
// Invalidate the whole area:
d3dImage.AddDirtyRect(new Int32Rect(0, 0, d3dImage.PixelWidth, d3dImage.PixelHeight));
d3dImage.Unlock();
}
}
Это было в основном, я надеюсь, что это поможет. Теперь несколько важных Примечаний:--6-->
- всегда блокировать изображение, чтобы избежать того, что WPF рисует кадры частично
- не звоните присутствующим на прямом 3D-устройстве. WPF представляет свой собственный backbuffer, основанный на поверхности, которую вы передали d3dImage.SetBackBuffer ().
-
событие IsFrontBufferAvailableChanged должно быть обработано, потому что иногда frontbuffer может стать недоступным (например, когда пользователь входит в экран блокировки). Вы должны освободить или приобрести ресурсы на основе доступности буфера.
private void d3dImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e) { if (d3dImage.IsFrontBufferAvailable) { initialize(); } else { // Cleanup: CompositionTarget.Rendering -= CompositionTarget_Rendering; DLL.Cleanup(); } }
Это было бы возможно с WINFORMS, вероятно, но не с WPF. Вот документация, рассказывающая о том, как WPF использует HWNDs:
как WPF использует Hwnds
чтобы максимально использовать WPF "hwnd interop", вам нужно понять, как WPF использует HWNDs. Для любого HWND вы не можете смешивать рендеринг WPF с DirectX рендеринг или GDI / GDI+ рендеринг. Это имеет ряд последствий. Прежде всего, чтобы смешать эти модели рендеринга вообще, необходимо создать решение для взаимодействия и использование назначенных сегментов взаимодействие для каждой модели отрисовки, которую вы решили использовать. Также, поведение рендеринга создает ограничение "воздушного пространства" для того, что ваш решение взаимодействия может выполнить. Концепция "воздушного пространства" более подробно описано в разделе Обзор технологических регионов. Все элементы WPF на экране в конечном счете поддерживаются HWND. Когда вы создаете окно WPF, WPF создает HWND верхнего уровня и использует Класс hwndsource для поместите окно и его содержимое WPF внутри HWND. Этот остальная часть содержимого WPF в приложении разделяет этот сингулярный HWND. Исключение составляют меню, выпадающие окна со списком и другие всплывающие окна. Эти элементы создают собственное окно верхнего уровня, поэтому меню WPF потенциально может пройти мимо края окна HWND, который его содержит. Когда вы используете HwndHost для размещения HWND внутри WPF, WPF сообщает Win32, как чтобы расположить новый дочерний hwnd относительно окна WPF HWND. Ля связанный концепция HWND-это прозрачность внутри и между каждым HWND. Это также обсуждается в разделе Обзор технологических регионов.
скопировано из https://msdn.microsoft.com/en-us/library/ms742522%28v=vs.110%29.aspx
Я бы рекомендовал вам изучить способ отслеживания вашей области рендеринга и, возможно, создать "отвратительное" дочернее окно перед ним. Другие исследования, которые вы можете сделать, это попытаться найти / получить графический буфер WPF и ввести визуализированный сцена непосредственно в него с помощью указателей и некоторых передовых программ памяти.
https://github.com/SonyWWS/ATF/ может помочь
Это редактор уровней с включенным представлением direct2d