I am trying to migrate an existing Winforms project into WPF. However: there are some user controls I need to leave as WinForm controls.
I have added a WinForms UserControl into a WPF Window. It consists of a RichTextBox and some buttons and labels. This is subclassed into various further user controls.
When I embed the UserControl into a WPF window it renders - but none of the buttons appear to do anything. When underlying processes update e.g. the RichTextBox it does not display the content. Yet when I inspect the textbox in debug I can see the content (though I have to click on 'base' to see this.)
[ One difference I have spotted - though it may not be relevant - is that when this control is on a WPF and non-working Visual Studio shows the object as 'sealed' but when in the original Winforms project when it is fully working it does not show as sealed. ]
I have added code to change the text in the labels - and they also firmly refuse to update: yet again I can see the text if I examine the label in debug mode.
This stack overflow question may address the same issue:
WindowsFormsHost Winform pdfviewer control problem
but the answer didn't make a lot of sense to me:
It mentioned replacing
new Window { Content = CreateContent(), Title = title }.Show();
But this is not a piece of code I recognise: I am using a xaml file with code behind and it's called up using
System.Windows.Application app = new System.Windows.Application();
app.Run(new FormWPFApp());
(where FormWPFApp is my name for the WPF window)
Here is the xaml header:-
<Window x:Class="ZedApp.FormWPFApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Printers="clr-namespace:ZedApp.UserControls.Printers"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
Title="Conversion version" Height="661" Width="1559" Loaded="Window_Loaded">
Here is the xaml I use for the two UserControls (they both inherit from the same base class) :-
<WindowsFormsHost Height="430" HorizontalAlignment="Left" Margin="192,32,0,0" Name="windowsFormsHostTicketPrinter" VerticalAlignment="Top" Width="324" Grid.Row="1" Grid.Column="1">
<Printers:TicketPrinter x:Name="ticketPrinter">
</Printers:TicketPrinter>
</WindowsFormsHost>
<WindowsFormsHost Height="430" HorizontalAlignment="Left" Margin="522,32,0,0" Name="windowsFormsHostJournalPrinter" VerticalAlignment="Top" Width="324" Grid.Row="1" Grid.Column="1">
<Printers:JournalPrinter x:Name="journalPrinter">
</Printers:JournalPrinter>
</WindowsFormsHost>
[Another thing I have noticed is a method that clears the Rich Text Box on one of the windows starts kicking out errors of the following type if run under WindowsFormsHost in WPF -
"Invoke or BeginInvoke cannot be called on a control until the window handle has been created."
private void ClearRichTextBox(RichTextBox rtbToClear)
{
if (rtbToClear.IsHandleCreated)
{
if (rtbToClear.InvokeRequired)
{
this.Invoke(new Action<RichTextBox>(ClearRichTextBox), new object[] {rtbToClear});
return;
}
rtbToClear.Clear();
}
}
]
What is the likely cause of this behaviour and what do I need to do to get the elements within the User Control working?
Proper input interop with WinForms requires some cooperation between the host and the WPF input system. The topic Message Loops Between Win32 and WPF in the SDK explains this well. In your setup, the easiest way to make this happen is to use code like this:
Window w = new Window1();
System.Windows.Forms.Integration.ElementHost.EnableModelessKeyboardInterop(w);
w.Show();
ElementHost.EnableModelessKeyboardInterop() essentially registers an input hook with the WinForms Application object (which normally runs the message loop) and calls ComponentDispatcher.RaiseThreadMessage().
Related
We have a WPF desktop application which needs to show some custom message windows. I am having trouble getting them to be read aloud properly by screen readers such as JAWS from Freedom Scientific.
I want to achieve the same behavior as when showing a system message box. For comparison, System.Windows.MessageBox.Show("my message", "My Caption); is announced by JAWS as "My caption dialog. My message. OK Button". This is perfect.
When my message windows are opened (containing only a TextBlock and OK Button), the window title is announced and the OK button is announced as having focus but the TextBlock message is not announced.
Here's a simple test application which shows the issue. Our real app has icons and other status text, of course.
<Window x:Class="Jaws_MessageBox_Test.MyMessageBox"
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:Jaws_MessageBox_Test"
mc:Ignorable="d"
Title="MyMessageBox" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="mainLabel" Grid.Row="0">Hi there, this is a test to see if JAWS will read the main textbloc when shown.</TextBlock>
<Button Grid.Row="1" Margin="5" HorizontalAlignment="Right" Padding="10,0,10,0" IsDefault="True" x:Name="closeButton" Click="closeButton_Click">_Close</Button>
</Grid>
</Window>
When I show this using:
var mb = new MyMessageBox();
mb.ShowDialog();
The screen reader announces: "MyMessageBox. Close Button" so it's not reading the TextBlock like the system message box does.
What I've found using the Windows SDK inspect and accevent tools is that
The system message box accessibility type is 'Dialog' but the WPF dialog's accessibility type is 'Window'. This might matter. There is no UI Automation Control Type of Dialog https://msdn.microsoft.com/en-us/library/ms749005(v=vs.110).aspx . Is this a bug or limitation in WPF perhaps?
I have tried setting various 'AutomationProperties' attached properties on my window so that the AutomationPeer will have better info but none of those are read when the ShowDialog runs.
Since TextBlock cannot receive input focus, there's no way to even get the text read by tabbing. I temporarily use a read-only TextBox instead to get focus but the experience is still wrong and our blind users should not have to tab around just to have a simple status message read to them.
As part of the experimenting, I also tried creating my own derived AutomationPeer for the message window but none of the Core method content is read automatically when the dialog is launched. The automation child list does have the title bar object listed as the first child whereas that's the last child for the system message box though I don't see a way to change that right now.
I'd greatly appreciate any help for creating a WPF-based custommessage box with full, proper accessibility for blind users.
You have to tell the automation API that your Window is a MessageBox.
To do that add this code to your Window
protected override AutomationPeer OnCreateAutomationPeer()
{
return new MessageBoxWindowAutomationPeer(this);
}
and add this class to your project
public class MessageBoxWindowAutomationPeer : WindowAutomationPeer
{
private const string WC_DIALOG = "#32770";
public MessageBoxWindowAutomationPeer(Window owner)
: base(owner)
{
}
protected override string GetClassNameCore()
{
return WC_DIALOG;
}
protected override string GetLocalizedControlTypeCore()
{
return "Dialogfeld";
}
protected override bool IsContentElementCore()
{
return true;
}
protected override bool IsControlElementCore()
{
return true;
}
}
As we don't need localization in our app "DialogFeld" is the german localized control type. Localizing that one is the part you would have to find out by yourself. ;-)
Set the AutomationProperties.HelpText on the run inside the Textblock
So for Example:
<TextBlock>
<Run Text="aTextString" AutomationProperties.HelpText="ATextString"/>
</TextBlock>
or
<TextBlock>
<Run Text="aTextString" AutomationProperties.HelpText="{Binding Text, RelativeSource={RelativeSource self}}"/>
</TextBlock>
OK from reading around the problem is with Jaws not WPF, as it tends not to read static text on Labels and TextBlocks - strange behaviour.
A workaround might be to use a TextBox, set the BorderStyle = None and place a rectangle on top of it, with fill = White, Opacity = 0.01. This will stop the user being able to focus on the TextBox and means the text will not be static and Jaws should read the text automatically . . .
One thing, does it have to be Jaws that reads the Dialogs that your App pops up?
Have you looked at using system.speech.synthesis.speechsynthesizer to speak the text when the dialog pops up - just a thought!
I don't know if this is correct solution but this works as required on JAWS 18.
<Window ...>
<UserControl>
<StackPanel>
<TextBlock Name="MessageText" ... />
<Button Name="OKButton" ...../>
</StackPanel>
</UserControl>
</Window>
and then focusing the button when window is loaded.
So I wrapped stackpanel inside the usercontrol element.
Both of these applications are rather old and have been built and maintained over several years by several people. At the moment, one of controls used in the WinForms project really needs to be displayed in the WPF project.
I've read about using WinForms controls in WPF projects, and for the most part if you're just instantiating a regular empty WinForm control, it seems relatively simple.
What I'm wondering is how you would best approach using part of a large project in another project? Ideally the WinForm control will be visible from within ONE of our WPF controls, on ONE tab, after having been sent and loaded the required data.
Here are some general guidelines.
From your WPF application, add project references to:
your WinForms project
WindowsFormsIntegration
System.Windows.Forms
Modify your XAML to include a WindowsFormsHost:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControl>
<TabItem Header="Old Form">
<WindowsFormsHost Name="WinFormsHost"></WindowsFormsHost>
</TabItem>
</TabControl>
</Grid>
</Window>
Instantiate your old Form and set it as the child of the WindowsFormsHost. Set TopLevel to false or it'll complain that "the child control cannot be a top-level form." Change the FormBorderStyle too, to prevent the Form's title bar from showing up and allowing the user to drag the Form around.
public MainWindow()
{
InitializeComponent();
WinFormsHost.Child =
new Form1 { TopLevel = false, FormBorderStyle = FormBorderStyle.None };
}
You end up with something like this:
You can read more in "Walkthrough: Hosting a Windows Forms Control in WPF" and the MSDN documentation for the "WindowsFormsHost Class".
I have a project created in C# in Visual Studio 2013 Express, a WPF application. when run with the debugger from Visual Studio, the application Window appears the correct size, but when running the same generated executable from the bin folder, the window is about 5-7px taller. It's worth mentioning that the Window.Height is controlled dynamically by the underlying code.
That said, why does the size differ during runtime like it does? I'm new to development on Windows.
Here's my code that does the resizing:
const int WindowBaseHeight = 94;
const int ItemHeight = 68;
ObservableCollection<obj> CurrentItems = new ObservableCollection<obj>();
AppWindow.Height = WindowBaseHeight + ItemHeight * CurrentItems.Count;
Ok. Take a deep breath, then delete your code and start all over.
First of all, if you're working with WPF, you need to leave behind any and all approaches you might be used to from other technologies and understand and embrace The WPF Mentality.
Basically, you never need to do any manipulations of layout, nor manual resizing of UI elements, nor anything like that in procedural code, because WPF is Resolution Independent by default and it provides several mechanisms to create auto-adjustable layouts and UIs that fit perfectly regardless of the available screen/window size.
In order to make the Window auto-size itself to it's contents' size, you must set the Window.SizeToContent property accordingly.
Another very important aspect that you need to understand is that in WPF, any UI that shows "items" (any UI that shows 2 or more of the same "thing", regardless what the "thing" is) should be implemented using an ItemsControl.
This is how you create an auto-adjustable items-based UI in WPF where the items have a height of 68:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" SizeToContent="Height" Width="200">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="68" Background="LightCyan">
<TextBlock Text="{Binding}"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Code behind (for the sake of the example):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Enumerable.Range(0, 4).Select(x => "Item" + x.ToString());
}
}
Result:
Notice how I'm using the ItemsControl and DataBinding it's ItemsSource property in order to declaratively define the UI in proper XAML instead of procedurally creating the UI in code. This is the preferred approach in WPF for everything.
This approach reduces code behind to practically zero. As you can see in my example, all I'm doing is setting the DataContext of the Window to a relevant piece of data that can be used for DataBinding.
Also notice that there is no code whatsoever doing any manipulations of the UI. The Window size is set by WPF according to the value of the SizeToContent property, which is set to Height.
I'm binding to a List<string>, for the sake of the example, but you can databind to any types of .Net objects in WPF, and even directly to XML.
WPF Rocks. - Simply copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
I suggest you read the linked material in this post, most importantly the "WPF Mentality" post, the DataBinding Overview, and the ItemsControl series. Let me know if you need further help.
I have a C# WPF app that has a WinForms control hosted in a WindowsFormsHost container. When I run my app the WinForms control does nothing in its OnPaint() event handler because the ParentForm property is NULL. In a pure WinForms project, this property is not NULL and is set to the Form the control was drawn on.
In my WPF project, since I can't figure out how to get the WinForms control to appear in the Designer, I added the control via the project XAML file. The control appears when the project is run but as I said, it never repaints because its ParentForm property is NULL.
In the main form's Window_Loaded() event I tried setting the WinForms control Parent property to the top level Window in the XAML file. I did not use the ParentForm property since it is read-only. However the compiler gives me this error:
Cannot implicitly convert type 'RawDataTestApp.Window1' to 'System.Windows.Forms.Control'
I assume I'm getting this error because Window1 is not a control? In any case, I don't know how to properly attach the WinForms control to the main form in my WPF project. How should I be doing that?
I've included the pertinent parts of the project's XAML file below. It's from an open source C# project. I added a WinForms graph control to the project hosted by a WindowsFormsHost container:
// Here's the source line that gets the error. pdeAffectiv is the WinForms graphcontrol that is hosted in a WindowsFormsHost container:
pdeAffectiv.Parent = MainForm;
// -------------------------- WPF Project XAML file ----------------------------------
<!-- Copyright © 2010 James Galasyn -->
<Window x:Name="MainForm" x:Class="RawDataTestApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EmotivEngine Realtime Client" Height="454" Width="1042" Background="#FF000033" Foreground="#FF33FFFF" xmlns:emoclient="clr-namespace:EmoEngineClientLibrary;assembly=EmoEngineClientLibrary"
xmlns:eecontrol="clr-namespace:EmoEngineControlLibrary;assembly=EmoEngineControlLibrary"
xmlns:local="clr-namespace:RawDataTestApp" Loaded="Window_Loaded" Closing="Window_Closing"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:graphlib="clr-namespace:GraphLib;assembly=GraphLib">
<Grid>
<!--- CONTENTS SNIPPED FOR BREVITY -->
<WindowsFormsHost x:Name="wfhAffectiv" HorizontalAlignment="Left" Height="148" Margin="530,75.493,0,0" VerticalAlignment="Top" Width="471" >
<graphlib:PlotterDisplayEx x:Name="pdeAffectiv" >
</graphlib:PlotterDisplayEx>
</WindowsFormsHost>
</Grid>
</Window>
I have this code in Thisaddin.cs
public void Search(string input)
{
ServerList listofservers = new ServerList();
listofservers.Visibility;
}
the ServerList is a simple WPF form with listbox thats it but how to display the listofservers?
I can't find the listofserver.show();
So first of all there is no item called WPF Form, there is only User Control for WPF. So once the WPF UserControl is created in the XAML you notice that this is the code
<UserControl x:Class="SQL_openertak2.ServerList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="454" d:DesignWidth="259" SizeToContent="WidthAndHeight">
<Grid>
<ListBox Height="410" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1" VerticalAlignment="Top" Width="242" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,427,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</UserControl>
So i have looked thru the XAML code. So as you can see that the whole thing is USERCONTROL
you have to change it to WINDOW then you will be able to see the .Show()
But take note that you also have to change the code in the xaml.cs
cause it will be like this by default
public partial class ServerList : UserControl
Change it to
public partial class ServerList : Window
well for obvious reasons!! :)
you can also host it in a layout panel, like:
Open Form1 in the Windows Forms Designer.
In the Toolbox, drag a TableLayoutPanel control onto the for
On the TableLayoutPanel control's smart tag panel, select Remove Last Row.
Resize the TableLayoutPanel control to a larger width and height.
In the Toolbox, double-click UserControl1 to create an instance of UserControl1 in the first cell of the TableLayoutPanel control.
The instance of UserControl1 is hosted in a new ElementHost control named elementHost1.
In the Toolbox, double-click UserControl1 to create another instance in the second cell of the TableLayoutPanel control.
In the Document Outline window, select tableLayoutPanel1. For more information, see Document Outline Window.
In the Properties window, set the value of the Padding property to 10, 10, 10, 10.
Both ElementHost controls are resized to fit into the new layout.
Change UserControl with Window as already answered in XAML and c# class.
Keep in mind that in VSTO applications, which are normally based on Windows Forms, it is important to remember to add System.XAML to references, otherwise you will probably get errors composing your forms layouts.
This could happen in VS2015 as I recently experienced, where the wizard procedure did not work as expected, missing to update class references.
Here some references: The type 'Window' does not support direct content