2015-01-10 30 views
12

Ich entwickle eine Zeichnung Anwendung in Visual C++ mittels Direct2D. Ich habe eine Demo-Anwendung, bei der:Redirect Direct2D Rendering zu einem WPF-Steuerelement

// 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 
      ); 

Und wenn ich die WM_PAINT Botschaft, die ich meine Formen zeichnen erhalten.

Jetzt muss ich ein WPF-Steuerelement (eine Art Panel) entwickeln, die mein neues Renderziel darstellt (daher würde es das Hauptfenster m_hwnd ersetzen), so dass ich ein neues (C#) WPF-Projekt mit dem Haupt erstellen kann Fenster, das als Kinder mein benutzerdefiniertes Panel hat, während der Rendering-Teil im nativen C++/CLI DLL-Projekt bleibt.

Wie kann ich das tun? Was muss ich als neues Renderziel festlegen?

Ich dachte, den Griff meiner WPF-Fenster zu verwenden:

IntPtr windowHandle = new WindowInteropHelper(MyMainWindow).Handle; 

Aber ich brauche auf meiner Platte und nicht auf meinem Fenster zu zeichnen.

Bitte beachten Sie, dass ich möchte keine WPF-Klassen für das Rendering Teil verwenden (Shapes, DrawingVisuals ...)

+2

helfen ich für ein Beispiel dafür, wie Host Direct2D in einer WPF-Anwendung suchen wäre. gute Frage. –

+0

Haben Sie Zugriff auf die DLL? Kannst du den Code ändern? Ich glaube, der Aufruf von CreateWindow() ist problematisch – Domysee

+0

WPF ist sehr nah an WIC, also würde ich stattdessen d2d1.CreateWicBitmapRenderTarget mit einer IWicBitmap von Ihnen verwenden. Diese IWicBitmap kann dann in einer Klasse verwendet werden, die von WPFs abstrakter BitmapSource stammt (genau wie InteropBitmap, aber mit einer eigenen Klasse, da WPF seine WIC-Bitmaps nicht freigibt ...). Diese BitmapSource kann jetzt ein untergeordnetes Element des Bereichs sein. Wenn Sie eine Beispiel-App zum Arbeiten haben, könnten wir dies weiter entwickeln. –

Antwort

3

Sie müssen eine Klasse implementieren, die Ihr Win32-Fenster m_hwnd hostet. Diese Klasse erbt von HwndHost.

Außerdem müssen Sie die HwndHost.BuildWindowCore und HwndHost.DestroyWindowCore Methoden außer Kraft setzen:

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 
} 

Bitte folgen Sie diesem Tutorial: Walkthrough: Hosting a Win32 Control in WPF.

0

Es wäre möglich, mit WINFORMS wahrscheinlich, aber nicht mit WPF. Hier ist eine Dokumentation darüber reden, wie WPF HWNDs verwendet:

Wie WPF HWNDs Verwendet

Um das Beste aus WPF "HWND Interop" zu machen, müssen Sie verstehen, wie WPF verwendet HWNDs. Für jedes HWND können Sie WPF-Rendering nicht mit DirectX Rendering oder GDI/GDI + -Rendering mischen. Dies hat eine Reihe von Implikationen. In erster Linie müssen Sie, um diese Rendering-Modelle zu mischen, eine Interoperationslösung erstellen und für jedes zu verwendende Renderingmodell bestimmte Segmente der Interoperation verwenden. Das Rendering-Verhalten erzeugt außerdem eine "Luftraum" -Beschränkung für das, was Ihre Interoperationslösung erreichen kann. Das "Luftraum" -Konzept ist im Thema Technologie Regionen Überblick näher erläutert. Alle WPF-Elemente auf dem Bildschirm werden letztendlich von einem HWND unterstützt. Wenn Sie ein WPF-Fenster erstellen, erstellt WPF eine Top-Level-HWND und verwendet eine HwndSource, um das Window und seinen WPF-Inhalt innerhalb der HWND zu setzen. Der Rest Ihres WPF-Inhalts in der Anwendung teilt diesen singulären HWND. Eine Ausnahme sind Menüs, Drop-Down-Menüs der Dropdown-Liste und andere Pop-ups. Diese Elemente erstellen ihr eigenes Top-Level-Fenster, weshalb ein WPF-Menü potenziell über den Rand des Fensters HWND, das es enthält, hinausgehen kann. Wenn Sie HwndHost verwenden, um ein HWND in WPF einzufügen, informiert WPF Win32 wie , um das neue unterordnete HWND relativ zu dem WPF-Fenster HWND zu positionieren. Ein verwandtes Konzept zu HWND ist Transparenz innerhalb und zwischen jedem HWND. Dies wird auch im Thema Übersicht über Technologieregionen behandelt.

Kopiert von https://msdn.microsoft.com/en-us/library/ms742522%28v=vs.110%29.aspx

Ich würde Sie empfehlen, einen Weg zu studieren den Überblick über Ihren Render-Bereich zu halten und vielleicht ein „abscheulichen“ Kind Fenster vor ihm erstellen. Andere Forschung, die Sie vielleicht tun können, ist zu versuchen und WPF Grafikpuffer zu finden und injizieren Sie Ihre gerenderten Szene direkt in es mit Zeigern und einige erweiterte Speicherprogrammierung.

+0

Wie wäre es mit Windows Forms möglich? – Nick

+0

In Windows-Formularen hat jedes Steuerelement ein Handle im C/C++ Win32 HWND-Format, Sie können es durch Aufruf der Control.Handle -Eigenschaft abrufen, es gibt ein IntPtr zurück, ich glaube, Sie können es in HWND umwandeln. Referenz: https://msdn.microsoft.com/library/system.windows.forms.control.handle%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 – Felype

2

Ich werde versuchen, die Frage so gut wie möglich zu beantworten, basierend auf WPF 4.5 Unleashed Kapitel 19. Wenn Sie nachschlagen möchten, finden Sie alle Informationen dort im Unterabschnitt "Mischen von DirectX-Inhalten mit WPF-Inhalt ".

Ihre C++ - DLL sollte 3 exposed Methoden Initialize(), Cleanup() und Render() haben. Die interessanten Methoden sind Initialize() und InitD3D(), die durch Initialisieren() aufgerufen wird:

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; 
} 

Ermöglicht dem Weg zu den XAML-Code:

xmlns:interop="clr-namespace:System.Windows.Interop;assembly=PresentationCore" 
<Button.Background> 
    <ImageBrush> 
     <ImageBrush.ImageSource> 
      <interop:D3DImage x:Name="d3dImage" /> 
     </ImageBrush.ImageSource> 
    </ImageBrush> 
</Button.Background> 

I festgelegt haben es als Hintergrund einer Schaltfläche hier, mit einem ImageBrush. Ich glaube, das Hinzufügen als Hintergrund ist eine gute Möglichkeit, den DirectX-Inhalt anzuzeigen. Sie können das Bild jedoch beliebig verwenden.

initialisieren die Wiedergabe ein Handle auf das aktuelle Fenster erfassen und die Initialize() -Methode des DLL mit nennen:

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; 
    } 
} 

Das CompositionTarget.Rendering Ereignis ausgelöst wird, kurz bevor die Benutzeroberfläche gerendert wird. Sie sollten Ihre DirectX Inhalt dort machen:

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(); 
    } 
} 

, die im Grunde war es, ich hoffe, es hilft. Jetzt nur noch ein paar wichtige Randnoten:

  • immer dein Bild sperren, um zu verhindern, dass WPF zieht Rahmen teilweise
  • Dont Anruf Gegenwart auf dem Direct 3D-Gerät. WPF stellt einen eigenen Backbuffer basierend auf der Oberfläche bereit, die Sie an d3dImage.SetBackBuffer() übergeben haben.
  • Das Ereignis IsFrontBufferAvailableChanged sollte behandelt werden, da der Frontpuffer manchmal nicht verfügbar sein kann (z. B. wenn der Benutzer den Sperrbildschirm eingibt). Sie sollten die Ressourcen basierend auf der Pufferverfügbarkeit freigeben oder abrufen.

    private void d3dImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
        if (d3dImage.IsFrontBufferAvailable) 
        { 
         initialize(); 
        } 
        else 
        { 
         // Cleanup: 
         CompositionTarget.Rendering -= CompositionTarget_Rendering; 
         DLL.Cleanup(); 
        } 
    } 
    
+0

ich nicht Direct3D verwenden können, muss ich Verwenden Sie Direct2D. – Nick

+1

afaik D3DImage kann auch Direct2D-Inhalte hosten. – Domysee

+1

@Nick Direct2D ist eine Bibliothek, die auf eine Untergruppe von Direct3D abzielt. https://msdn.microsoft.com/en-us/library/windows/desktop/dd370966(v=vs.85).aspx – Aron