How can I write a BringToFront method for a WPF UserControl? - c#

I am developing a UserControl, call it CoolControl, that is meant to act somewhat like a window, with a few special features. So far, it can be resized and dragged all around the screen. If I add multiple CoolControl objects to my application window using XAML, the last one that was declared is always in front. This is fine, but I want to make it so that if I click on one of my CoolControl objects during run-time, that control will put itself in front of all the other controls.
I've tried using Canvas.SetZIndex, but unless I'm simply unable to come up with a clever enough solution, I don't see how that can help me. Because once I set one control's Z-Index to 9999, over time every other control I click will have the same value of 9999. And then, once again, the control declared last ends up in front.
If you were given the task of writing a BringToFront() method for someone's UserControl, how would you do it in the simplest way possible? I'd prefer a better solution than getting the parent window, looping through all the controls, finding the maximum Z-Index, and then setting the Z-Index of the CoolControl accordingly, if THAT is even a valid solution.

I'm not familiar with the Canvas.SetZIndex method. It looks like some sort of attached property or behaviour.
If you can provide the logic to set the z-index, I've outlined a way to keep track of the instances and manage the z-indexes, keeping them in the order in which they have been selected/created.
public class CoolControl : UserControl
{
public CoolControl()
{
InitializeComponent();
Instances.Add(this);
}
static IList<CoolControl> Instances = new List<CoolControl>();
void SelectThisInstance()
{
foreach(var instance in Instances)
{
// decrement z-index instance
}
// set z-index of this instance to show at top
}
}

Related

How do you programmatically create new controls inside a custom WPF Panel implementation?

I'm creating an OverflowPanel derived from the WPF Panel class. The intent is that it will fill with items in a single direction, and when there are too many items to display, excess items will be removed and replaced with another control to hold the overflow. Think of a website's breadcrumbs, or the address bar in Windows File Explorer. This is a .Net Core 3/C# 8 project.
I have a partially working solution: I've inherited from Panel and overridden MeasureOverride() and ArrangeOverride() to get the behavior I want. My problem now is getting a button or some other control to display in place of the items being removed.
My initial, naive approach was to just create a Button in code and try to Measure/Arrange it.
public class OverflowPanel : Panel
{
// First by itself, but I did also try to host this in a new UIElementCollection
private readonly Button _overflowButton = new Button();
public override Size MeasureOverride(Size availableSize)
{
...
_overflowButton.Measure(availableSize);
// Do stuff with _overflowButton.DerivedSize.
...
}
// Also attempted to draw int in ArrangeOverride()
}
This did give me non-zero result for the measurement. (I put some dummy content in the button.) My algorithm gives me space on the screen where the button should go, however, nothing gets rendered there.
I also confirmed that there wasn't simply a button being drawn with no visual style, by inspecting the Live Visual Tree in Visual Studio.
I tried to make a UIElementCollection and add the button to that to see if it would add it to the visual tree, but this also did not work.
Most Google/StackOverflow results I've seen suggest something along the lines of this.Children.Add(_overflowButton), but this does not work when hosted inside an ItemsControl, as it takes over managing the collection of objects and throws an exception if you attempt to mess with it.
After digging around in the code for Panel and UIElementCollection, I noticed that Panel lets you override
UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
to use a derived implementation of UIElemenetCollection. I created a PinningUIElementCollection to trick WPF into rendering the extra element. It stores extra items and then slips them in whenever the iterator is accessed. It also does index mangling to access both the extra collection of items and the automatically generated one.
This actually worked. My button is now displayed (albeit without the correct styling, but that's a separate issue.)
However my issue with this approach is that it seems like a lot of work. It also seems error prone: I could easily miss when it tries to use a numerical index and forget to mangle it, causing unpredictable results.
Is there a simpler/more straightforward way, in my derived Panel implementation, to display an extra button or some other arbitrary control with only a few less hoops?

Correct UserControl Usage?

I just started breaking up my GUI application into UserControls. I have a TabControl with a bunch of TagePages. Obviously my MainForm.cs file was filled up with tons of events and controls etc and it got very messy quick.
So a previous question gained me the insight of how to create a UserControl. I intend on creating a UserControl for each TabPage and I was wondering how I can interact with Components on the main form or other UserControls.
Here is an example of a TabPage that I have made using a UserControl, which needs to Enable or Disable a button depending which TabPage is currently selected. Is this proper usage or is there a better way?
public partial class TabDetails : UserControl
{
private RequestForm fRequestForm;
public TabDetails()
{
InitializeComponent();
}
public void CustomInitialization(RequestForm pRequestForm)
{
fRequestForm = pRequestForm;
pRequestForm.TabControl_Main.SelectedIndexChanged += SelectedTabIndexChanged;
}
private void SelectedTabIndexChanged(object pSender, EventArgs pEvents)
{
fRequestForm.Button_SubmitRequest.Enabled = fRequestForm.TabControl_Main.SelectedTab != fRequestForm.Tab_Details;
}
}
In the MainForm.cs constructor I call:
this.tab_Details1.CustomInitialization(this);
This doesn't look like a good use of a user control. The user control should not decide how things in the form should behave when something is changed in the user control. A user control should be unaware of its container and should operate in any container.
The user control should notify the form that something has changed without telling what's the internal implementation and the form should decide what to do.
Example:
A user control named "NameUserControl" consists of TitleComboBox, FirstNameTextBox and LastNameTextBox. The user control wants to notify when one of the values has changed.
Wrong Way:
Create events:
TitleComboBox - SelectedIndexChanged.
FirstNameTextBox, LastNameTextBox - TextChanged.
The problems here:
You expose the internal controls behavior. What will happen if you want to change the TitleComboBox to TextBox? You'll have to change the event name and implementation.
You expose the fact that you use exactly 3 different controls. What will happen if you want to use the same text box for first and last name? You'll have to delete one event and change the name of the other.
Good Way:
Create only a single event: NameChanged and expose 1 property of FullName or three different properties for the values.
Either way the form subscribe to the event and decide what to do next.
Another thing to think about: the more you add more functionality to your user control, you either make it less reusable or you make its code more complex. For example, if you add validation inside the user control, you'll find one day that you need it without validation, so you'll add a property "bool ValidateData" or it will be so complicated that you'll need to build another control. One way to solve that is to build very small user controls, but combine them in one or more bigger user controls that fit all your current needs.

C# Using a form to load other user controls and have access to a base control or property

Currently I have a C# program with a windows form and then a user control template put onto the form. The user control template is really just used as a placeholder. I have a series of other controls which inherit from this user control template.
Each of those controls have navigation buttons like 'Continue' and 'Back' on them and each control knows which control needs to be loaded next. However what I need to figure out is an easier way to have variables that are global to these controls.
The only workaround I have is that I pass the form to each control when they are loaded and use variables inside of the form to read and write to. What would be the proper way to have each of these user control screens be built off of a base control which contained objects all of the controls could get to?
Sorry for the rambling nature of the post but I've been thinking about this problem all morning.
Here is some of the code:
Most of what I have written was based on hiding and showing the user controls so that content in the controls wouldn't be lost during navigation. I won't be needing to do that as eventually it will be loading the fields of data from a database.
Code for initially loading control from form click:
conTemplate1.Controls.Clear();
conInbound Inbound = new conInbound(this);
Inbound.Dock = DockStyle.Fill;
Inbound.Anchor = (AnchorStyles.Left | AnchorStyles.Top);
conTemplate1.Controls.Add(Inbound);
Code for Continue button inside of one of the controls:
if ((Parent.Controls.Count - 1) <= Parent.Controls.IndexOf(this))
{
UserControl nextControl = new conPartialClear();
nextControl.Dock = DockStyle.Fill;
Parent.Controls.Add(nextControl);
this.Hide();
Parent.Controls[Parent.Controls.IndexOf(this) + 1].Show();
}
else
{
this.Hide();
Parent.Controls[Parent.Controls.IndexOf(this) + 1].Show();
}
The best-practice for communicating from a control to a parent is to use events, and for communicating from a parent to a control is to call methods.
However, if you don't want to or can't follow this practice, here's what I would recommend.
Each UserControl has a ParentForm property that returns the Form that contains the control. If you know that the UserControl will always be attached to MyParentForm, you just cast the ParentForm and then you can access all public controls, methods, etc.
Here's what I mean:
public class conTemplate
{
public MyParentForm MyParentForm
{
get
{
return (MyParentForm)this.ParentForm;
}
}
}
This way, you can easily access any public members of MyParentForm. Your conInbound class could have code such as this.MyParentForm.GlobalSettings.etc..., and could even have access to any public controls.
I'm not totally sure I understand your problem. It sounds like you want the user control to "do something" with it's parent form. If that's the case, you may want to consider adding events to the UC and then handle them on the form itself.
Basically, for your UC's "continue", you'll have an event that's fired when it's pressed. You'll want to handle that in your form. I'm not real sure about the syntax from memory, or I'd work something out for you code-wise. But I think that's the route you'll want to take. Think of your UC like any other windows form control. If you add a button to your form, you assign it it's event method. Do the same with the UC.
I found this and thought it may be helpful. Scroll down to where it talks about UC's and events.
http://www.akadia.com/services/dotnet_user_controls.html
Hope this helps.
EDIT after new info from OP.
You could declare a global variable inside the UC of type yourForm and then set that variable to the ParentForm at run-time, if I'm understanding you correctly.
So, inside your UC Class, you could do:
private parentFormInstance;
then inside the constructor of the UC, you could set it as such:
parentFormInstance = this.ParentForm; (or whatever the property name is).
This allows you at design-time to use:
parentFormInstance.DoSomething();
without the compiler yelling at you.
Just basic advice, but if you can go back and make it easier on yourself, even if it takes some additional time re-working things, it'd be worth it. It may save you time in the long run.

What is the best way to access elements of parent control from a child control?

I have a parent control (main form) and a child control (user control). The child control has some code, which determines what functions the application can perform (e.g. save files, write logs etc.). I need to show/hide, enable/disable main menu items of the main form according to the functionality. As I can't just write MainMenu.MenuItem1.Visible = false; (the main menu is not visible from the child control), I fire an event in the child control and handle this event on the main form. The problem is I need to pass what elements of the menu need to be shown/hidden. To do this I created an enum, showing what to do with the item
public enum ItemMode
{
TRUE, FALSE, NONE
}
Then I created my eventargs which have 6 parameters of type ItemMode (there are 6 menu items I need to manage). So any time I need to show the 1st item, hide the 2nd and do nothing with the rest I have to write something like this
e = new ItemModeEventArgs(ItemMode.TRUE, ItemMode.FALSE, ItemMode.NONE, ItemMode.NONE, ItemMode.NONE, ItemMode.NONE);
FireMyEvent(e);
This seems like too much code to me and what's more, what if I need to manage 10 items in future? Then I will have to rewrite all the constructors just to add 4 more NONEs.
I believe there's a better way of doing this, but I just can't figure out what it is.
you could create an EventArgs which takes an ItemMode[] or a List<ItemMode> or a Dictionary<string, ItemMode> for those items (instead of the current 6 arguments) - that way you don't need to change much when adding more items...
The chain child->parent can be reversed. In such scenario requests will be passed from the mainform to its child controls.
Controls participating in the command processing must implement a special interface:
interface ICommandHandler
{
bool CanInvoke(int commandId);
void InvokeCommand(int commandId);
bool UpdateCommand(int commandId, MenuItem item);
}
The advantage of this approach is that only active controls must be traversed, not all the children.
The weak point - UpdateCommand() method, which could be called from Application.Idle event or timer.
hope this helps
Well, I can't speak to a "best" way unless except in specific cases, since there are often several equally good ways. My first thought, though, would be to create a class that has a property which the parent assigns a reference of its MainMenu, and which has functions for enabling/disabling individual menus or items. In a very simple case, this could be as simple as passing a list of strings like "OptionsScreen=enabled" etc. and then inside the class manually handling those cases, to something more generic like passing strings such as "mnuToolsOptions=enabled" and then finding the menu item via the .Name property. So, on startup, create an instance of your menu handler class, then do something like MenuHandlerHelper.MenuToHandle = MainMenuStrip;.
On the child side, you could perhaps have your classes that update the MainMenu be derived UserObjects that derive from a common one you create that has a public MyMainMenuHandlerHelper MenuHandlerHelper property, and set that in your Parent form's constructor so the Child controls could call the menu updating function. Or, you could have an event that just passed back a List<string> containing all the rules, and fire that as you are doing now.
This is a very simple idea, and doesn't handle things like possible collisions, so you would probably either want to throw an exception (easiest). You might also want to have rule priorities (easy), or try to chain functionality (could be hard to determine orders and such).
I would be happy to implement some examples of my thinking if you can constrain the problem a little for me (desired collision handling, etc.) and I actually wanted to see what some basic code would look like and try perhaps to test a couple of ideas, so if those come to anything I will post the code here for those as well.
If you want to handle all changes from the user control: you could inherit your own user control class and add a reference to the form/collection of menu entries you want to be able to modify. You would pass this reference to its constructor and then you'll be able to easily modify the menu from inside your user control
If, on the other hand, you would like to manage this on an event basis in your form, you could implement your own EventArgs class, but I would do it like this:
class ItemModeEventArgs
{
MenuItemClass target;
EnumType change;
}
So basically for each menu item a separate event is risen. Every event args knows about what item menu is changing and how it is changing. Ofc, if you only have two states for the menu items, the 'change' field is kinda useless.
This way you don't have to hardcode functions with n parameters where n is the number of menu items.
There truly are many ways this could be done. The easiest way, although some will shout "bad practice", would be to just pass a pointer to the main menu when the control is created. Your control would have some code like this:
MenuStrip MainMenu;
internal void SetMainMenu(MenuStrip mainMenu)
{
MainMenu = mainMenu;
}
and when you create the control:
void CreateControl()
{
MyUserControlType MyControl = new MyUserControlType();
MyControl.SetMainMenu(mainMenuStrip); //or whatever you called your main menu
}
This will give your child form unlimited access to the mainform's menu (which is why it's technically a bad practice). From the child form you can access the submenus by name, eg:
if (MainMenu != null)
{
ToolStripMenuItem fileMenu =
(ToolStripMenuItem)MainMenu.Items["fileToolStripMenuItem"];
fileMenu.DropDownItems["exportFileToolStripItem"].Visible = false;
}
If you created the control in the designer, then you can add the SetMainMenu call into the .design file, or add it in the Form's load event.

Bring Winforms control to front

Are there any other methods of bringing a control to the front other than control.BringToFront()?
I have series of labels on a user control and when I try to bring one of them to front it is not working. I have even looped through all the controls and sent them all the back except for the one I am interested in and it doesn't change a thing.
Here is the method where a label is added to the user control
private void AddUserLabel()
{
var field = new UserLabel();
userContainer.Controls.Add(field);
SendLabelsToBack(); // Send All labels to back
userContainer.Controls[field.FieldName].BringToFront();
}
Here is the method that sends all of them to the back.
private void SendLabelsToBack()
{
foreach (var label in userContainer.Controls);
label.SendToBack();
}
Yeah, there's another way. The Controls.SetChildIndex() also changes Z-order. The one with index 0 is the one on top. Doesn't buy you anything though, BringToFront() uses this method.
Your SendLabelsToBack() method as given cannot work, it will also send the label to added to the back. But your next statement fixes that again.
Okay, that doesn't work, which means the BringToFront() method doesn't get executed. Look in the Output window for a "first chance exception" notification. As written, your SendLabelsToBack() will cause an exception if the user control contains any control other than a UserLabel. Also, set a breakpoint after the BringToFront() call and check the value of userContainer.Controls[0].Name when it breaks.
Controls' z-index is per-container.
If you call BringToFront on a control that is inside a container (such as a Panel), it will not bring the container to the front.
Therefore, the control will only go in front of other controls in that container.
To see what containers your controls are in, you can use the Document Outline pane in the View menu.
EDIT: Your userContainer control is probably behind a different control.
Have you tried Invalidate() after BringToFront()? BringToFront does not raise the Paint event
try this:
private void SendLabelsToBack()
{
foreach (var label in userContainer.Controls)
{
label.SendToBack();
label.Invalidate();
}
}
I think you just need to change your last line:
userContainer.Controls[field.FieldName].BringToFront();
to this:
userContainer.Controls[field.Name].BringToFront();
When you use a string as the indexer for the Controls collection, it goes by the Name property of the control (not the FieldName property).
Since you're just trying to bring the most recently-added control to the top, this would also work:
userContainer.Controls[userContainer.Controls.Count - 1].BringToFront();
From my experience looks like windows puts all controls belonging to one graphic container(pane, group box...etc) in a software collection. The collection is ordered by child index which is a property of every control in that container.
The trick is that children with the same index can and do exists. In this case windows will paint those children ordered relative to others but between them it will paint them in the reverse order they had been added to the container.
Long story short: for one container-you need to make sure controls have different indexes by changing ALL NOT just SOME of the indexes when you want to change the z-order. For example:
foreach (Control newControl in TopControl.Controls)
{
TopControl.Controls.SetChildIndex(newControl,indexlogic(newControl));
}
where indexLogic(newControl ) is your method of calculation of the index of particular control.

Categories