Force ElementHost to redraw - wpf

We have a software that uses Windows Forms's built-in designer (IDesignerHost et al), and custom user controls you can place on the surface.
I'm moving portions of those controls to WPF, using ElementHost. When loading those controls and adding them programmatically, that works, fine. However, when the user interactively adds a new control, its ElementHost portion (in this example, the label at the top) is rendered entirely in black.
Until, that is, I move it around on the design surface.
So clearly, something can be done to force the ElementHost to draw its contents correctly. I'm just not entirely sure what.
There's a number of posts (e.g., ElementHost Layout Problems, ElementHost - Black Background when calling Show method, Black background before loading a wpf controll when using ElementHost), but all of them sound like hacks that don't solve the actual underlying problem.
(edit)
Here's the existing IDesignerHost.CreateComponent implementation:
<System.Diagnostics.DebuggerStepThrough()>
Public Function CreateComponent(componentClass As Type, name As String) As IComponent Implements IDesignerHost.CreateComponent
Dim Component As IComponent = Nothing
' Create instance
Component = CType(Activator.CreateInstance(componentClass), IComponent)
' Add to design container
Add(Component, name)
Return Component
End Function
Amending it with a Refresh() like so doesn't help:
[System.Diagnostics.DebuggerStepThrough]
public IComponent CreateComponent(Type componentClass, string name)
{
IComponent Component = null;
Component = (IComponent)Activator.CreateInstance(componentClass);
// Add to design container
Add(Component, name);
if (Component is UserControl newUC)
{
newUC.Refresh();
}
return Component;
}
Toggling Visible does seem to fix the issue:
[System.Diagnostics.DebuggerStepThrough]
public IComponent CreateComponent(Type componentClass, string name)
{
IComponent Component = null;
Component = (IComponent)Activator.CreateInstance(componentClass);
// Add to design container
Add(Component, name);
if (Component is UserControl newUC)
{
newUC.Visible = false;
newUC.Visible = true;
}
return Component;
}
But this seems quite hack-ish to me.

Related

Is it possible to create a control once and have it generated everytime it is needed?

Say for example you have a stackpanel that you would like to programmatically add buttons to.
The code behind for the button generation and addition to the stackpanel is:
Button button = new Button();
button.Content = "Button";
button.HorizontalAlignment = HorizontalAlignment.Left;
button.Name = "Button" + i
stackPanel1.Children.Add(button);
My question is - Is it possible to generate the button once and have it as some kind of template that can be added to the stackpanel whenever it is needed without going through the generation code again?
In WPF each UIElement can only be the logical child of one control at any given time, see WPF Error: Specified element is already the logical child of another element. Disconnect it first, so no you can't use the same button and add it to another control later on, unless you're sure you've gotten rid of that stackpanel
But, you can do recycling. See Optimizing Performance: Controls. especially if you are willing to override MeasureOverride and ArrangeOverride of you stackpanel
I've actually written such a recycler because I had grids with many controls, and I wanted to implement some kind of virtualizing grid. These are the main methods of my class:
internal class Recycler
{
private UIElementCollection _children;
public Recycler(UIElementCollection children)
{
_children = children;
//You need this because you're not going to remove any
//UIElement from your control without calling this class
}
public void Recycle(UIElement uie)
{
//Keep this element for recycling, remove it from Children
}
public UIElement GiveMeAnElement(Type type)
{
//Return one of the elements you kept of this type, or
//if none remain create one using the default constructor
}
}

Windows Narrator reads the names of all the controls in the window (even hidden ones)

I need to make my application visually impaired friendly... and I am facing this problem: Windows Narrator reads all the controls names in the window despite that some of them are hidden.
I have another app that I used WinForms to write it, and there it works fine.
After looking in the UI Spy I saw that WinForms app is not exposing hidden controls and WPF is exposing all the controls in the window.
Can it be that it's a bug in WPF?
I was having the same problem.
Based on Alexis's answer, I wrote the code bellow. It works for me.
public class MyAutoComplete : RadAutoCompleteBox
{
public MyAutoComplete ()
{
//init stuff here
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new MyAutomationPeer(this);
}
}
internal class MyAutomationPeer : RadAutoCompleteBoxAutomationPeer
{
public MyAutomationPeer(FrameworkElement owner)
: base(owner)
{
}
protected override List<AutomationPeer> GetChildrenCore()
{
return new List<AutomationPeer>();
}
}
If your controls are already in the visual tree, this behavior is the normal one, because UI Automation tree based on the Visual tree. So if you want to prevent of reading unnecessary elements using screen readers, you have to load them on demand.
You can also override the OnCreateAutomationPeer method in controls that contain visible and hidden elements to return your own AutomationPeer. Then you can override the GetChildrenCore method and return modified children collection. To update automation children tree, you need to call the AutomationPeer.ResetChildrenCache() method and the AutomationPeer.RaiseAutomationEvent(AutomationEvents.StructureChanged) one.

Prism, Unity, and Multiple Views ala MDI

I'm trying to create an application similar to Visual Studio in that we have a main content area (i.e. where documents are displayed in a TabControl, not a true MDI interface), with a menu on the side.
So far, I have everything working, except the content. My goal is that when a user double clicks on an item in the navigation menu on the side, it opens the document in the Content region. This works, but every time I double click it spawns a new instance of that same view. There's a chance that I could have multiple views of the same type (but different "names") in the TabControl content container.
Right now, my code looks something like this...
IRegion contentRegion = IRegionManager.Regions[RegionNames.ContentRegion];
object view = IUnityContainer.Resolve(viewModel.ViewType, viewModel.UniqueName);
if (!IUnityContainer.IsRegistered(viewModel.ViewType, viewModel.UniqueName))
{
IUnityContainer.RegisterInstance(viewModel.UniqueName, view);
contentRegion.Add(view);
}
contentRegion.Activate(view);
However, it appears that the view is never registered, even though I register it... I imagine I'm probably doing this wrong -- is there another way to do this? (re: the right way)
So, the problem was trying to do it this entire way. The smart method (for anyone else trying to do this) is to make use of Prism the correct way.
What I ended up doing was instead Navigating by:
1. In the Navigation Menu, constructing a UriQuery (included in Prism) with the UniqueID of the view I want to display (which is guaranteed to be unique) and adding that to the View I wanted to navigate to, i.e.:
IRegionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(ViewNames.MyViewName + query.ToString(), UriKind.Relative));
where query is the UriQuery object.
2. Register the View and ViewName in the Module via:
IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
container.RegisterType<object, MyView>(Infrastructure.ViewNames.MyViewName);
3. In the View, make sure the ViewModel is a parameter on the constructor. Let Prism inject this manually for us. Inside the constructor, make sure you set the DataContext to the incoming ViewModel.
4. Finally, make sure your ViewModel implements INavigationAware interface... This is a very simple implementation of it (UniqueID is a property on the ViewModel):
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
return (navigationContext.Parameters["UniqueID"] == UniqueID);
return false;
}
public virtual void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public virtual void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
UniqueID = navigationContext.Parameters["UniqueID"];
}
From here, Prism will ensure that only one view of your "UniqueID" will exists, while allowing for others of the same view, but different ViewModel (or data for that ViewModel, i.e. viewing two users in different tabs, but both use the same templated view).

how to Show MDIChild Form on Top of the MDIParent's Controls

I have a MDI-Parent Form with many ChildForms, when I want to add a control on my Parent form, Child form appears under the control, For example I want to add a groupbox and a PictureBox on MDIParent Form, but when I call the Child Form it appears Under these controls.
frmChildForm1.TopMost=true doesn't works either.
I have attached a photo for more description.
What can I do?!
but I want to have an Image as Background
That's possible, you can set the BackgroundImage property of the MDI client control. The only obstacle is that you cannot directly get a reference to that control. You have to find it back by iterating the form's Controls collection. Like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
foreach (Control ctl in this.Controls) {
if (ctl is MdiClient) {
ctl.BackgroundImage = Properties.Resources.Lighthouse;
break;
}
}
}
}
Where Lighthouse was a sample image I added as a resource. Change it to use your own. Another common technique is to subscribe the Paint event for that control and draw whatever you want. A gradient is a common choice.

usercontrol adding another usercontrol programmatically in design time

I have a usercontrol (UC1) that changes aspect at design time according to what the user wants to show.
A regular button that pops a window with usercontrol UC2 (the window is only shown at runtime)
The UC2 directly hosted in UC1 (the regular button is then not shown)
Since I want to use the same UC2 instance in both situation, I just transfer ownership between UC1 and the form.
public UC1 ()
{
_uc2 = new UC2 ();
}
public bool DisplayModeSimple
{
get { return _displayModeSimple; }
set
{
_displayModeSimple = value;
if (_displayModeSimple)
{
// ... Verify if _uc2 is already in Controls...
Controls.Remove (_uc2);
uiButton.Visible = true;
}
else
{
// ... Verify that _uc2 is not in Controls ...
Controls.Add (_uc2);
uiButton.Visible = false;
}
}
}
private void HandleButtonClick (object sender, EventArgs e)
{
// Not called if DisplayModeSimple=false since button is hidden...
using (var form = new PopupForm (_uc2))
{
form.ShowDialog (this);
}
}
Works fine in both design and runtime mode.
In design mode if I change the display mode UC1 behaves correctly.
However, controls that are on UC2 can be clicked like if it was runtime.
If I then close the form hosting UC1 and reopen it everything is back to normal, i.e., I cannot "click" on any controls in UC2.
The problem is that your first UserControl is hosted on VS, so it knows to be in design mode. The second UserControl is hosted in the first UserControl, so as its host is not a Designer, it thinks to be in a normal container and behaves accordingly. How to solve that is a bit tricky, as there isn't asimple solution AFAIK. Here you can find some workarounds. Another could be to test Site.DesignMode recursively, but it depends on the level of depth of your controls.

Resources