WinForms meets WPF meets WinForms meets WPF
Modaler Printdialog- Aufruf in einer gemischten WPF/WinForms Anwendung
Ausgangslage:
Eine bestehende WinForms Applikation soll um eine Teilkomponente erweitert werden. Da die neue Komponente grafisch anspruchsvolle Inhalte darstellen soll (dynamisch skalierbares Diagramm), wird dessen Benutzeroberfläche mit WPF realisiert.
Die Einbettung von WPF UserControls in einer WinForms Applikation an sich stellt keine Probleme dar, da diese Problematik durch Framework Komponenten wie folgt gelöst werden kann.
Host Applikation (WinForms):
namespace WinFormsHost { using System.Windows.Forms; using WpfControlLibrary; public partial class MainWinForm : Form { public MainWinForm() { InitializeComponent(); // elementHost is defined & initialized in MainWinForm.Designer.cs file: // private System.Windows.Forms.Integration.ElementHost elementHost; // this.elementHost = new System.Windows.Forms.Integration.ElementHost(); elementHost.Child = new ModalPrintDialogControl(); } } }
Eingebettetes WPF UserControl (ModalPrintDialogControl):
XAML:
"WpfControlLibrary.ModalPrintDialogControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
und dazugehörendes code behind cs.-file:
namespace WpfControlLibrary { using System.Windows; using System.Windows.Controls; public partial class ModalPrintDialogControl : UserControl { public ModalPrintDialogControl() { InitializeComponent(); } } }
Natürlich liegt es nahe, dass die neue Komponente auch eine Printfunktion enthalten muss. Auch da bietet das Framework einfache Mittel, um grafische WPF Elemente auszudrucken:
Anpassung im XAML:
Anpassung im code behind file:
private void ButtonPrintClicked(object sender, RoutedEventArgs e) { var printDialog = new PrintDialog(); printDialog.ShowDialog(); // prints the whole representation of the WPF Control printDialog.PrintVisual(this, "My Diagram"); }
ShowDialog() ruft den Printdialog modal auf. Das heisst, währendem der Printdialog geöffnet ist, soll die restliche Applikation (sowohl die neue WPF Komponente wie auch die schon bestehende WinForms Hostapplikation) gesperrt sein.
Und hier kommen wir zum eigentlichen Problem:
Während dieser modale Printdialog Aufruf in reinen WPF Applikationen problemlos funktioniert, erscheint der Printdialog im beschriebenen Scenario als nicht modaler Dialog. Dies hat zur Folge, dass trotz des geöffneten Printdialoges die Applikation selber weiter bedienbar ist und somit theoretisch ein mehrfaches gleichzeitiges Aufrufen des Printdialoges ermöglicht wird:
Lösungsansätze:
Natürlich ist dieses Verhalten nicht zu gebrauchen und somit ist ein neuer Lösungsansatz nötig um den Printdialog modal aufrufen zu können:
Lösungsansatz 1:
Da das eigentliche Problem ist, dass die Hostapplikation den Printdialog nicht als modalen Dialog erkennt, könnte statt des oben verwendeten Printdialog dessen Pendant aus dem WinForms Namespace (System.Windows.Forms) verwendet werden:
PrintDialog winFormsPrintDialog = new PrintDialog();
winFormsPrintDialog.ShowDialog();
Dieser WinForms Dialog wird von der Hostapplikation erkannt und modal aufgerufen. Doch die Nachteile dieses Print-Dialoges sind beträchtlich, da nicht wie beim oben beschriebenen Printdialog aus dem System.Windows.Controls Namespace direkt mittels PrintVisual(..) ausgedruckt werden kann.
Lösungsansatz 2:
Verwendet soll also der ursprüngliche Printdialog aus dem System.Windows.Controls Namespace. Da aber dieser aber nicht direkt als modaler Dialog aufgerufen werden kann, wird nun ein weiteres WinForm zur Hilfe genommen.
Statt aus der WPF Komponente direkt den Printdialog aufzurufen, wird dieser in ein WinForms Control “verpackt”. Die WPF Komponente ruft nun dieses WinForms Control modal auf. Da diese auch von der Hostapplikation erkannt wird, ist die ganze Applikation gesperrt solange der Printdialog offen ist.
WinForms Control welches den WPF Printdialog aufruft:
namespace WpfControlLibrary { using System; using System.Windows.Forms; public class PrintDialogHost : Form { // wpf print dialog instance. private System.Windows.Controls.PrintDialog selectedPrintDialog; private PrintDialogHost() { Load += PrintDialogHostLoad; } // Returns instance of WPF print dialog or null if // the user cancel the print dialog window. public static System.Windows.Controls.PrintDialog GetPrintDialogInstance() { System.Windows.Controls.PrintDialog printDialog; using (PrintDialogHost instance = new PrintDialogHost()) { instance.ShowDialog(); printDialog = instance.selectedPrintDialog; } return printDialog; } private void PrintDialogHostLoad(object sender, EventArgs e) { var pd = new System.Windows.Controls.PrintDialog(); selectedPrintDialog = pd.ShowDialog() == true ? pd : null; Close(); } } }
Natürlich haben wir nun das störende WinForm welches jedes Mal zusätzlich zum Printdialog erzeugt und dargestellt wird. Dieses wird zwar zwingend benötigt, kann aber über einige Einstellungen für den Benutzer unsichtbar aufgerufen werden:
private PrintDialogHost() { ShowInTaskbar = false; WindowState = FormWindowState.Minimized; Load += PrintDialogHostLoad; }
Anregungen oder alternative Lösungen sind sehr willkommen.