I found out that child controls are not drawn outside of their parent's bounds. So if I have a button that is sticking out of the panels right side it's drawn half way to the part where the panel ends.
2 questions:
Is there any way to have child controls outside of parents bounds (with some trick maybe) ?
What is the reason for that limitation ? (I want to understand that)
Example with 2 buttons: b1 is the big parent, b2 the smaller child:
public Form1()
{
Size = new Size(800, 600);
Button b1 = new Button();
b1.Text = "hello world";
b1.SetBounds(0,0,256,128);
Controls.Add(b1);
Button b2 = new Button();
b2.Text = "abcdefghijklmnopqrstuvwxyz";
b2.SetBounds(192,32,128,64);
b1.Controls.Add(b2);
}
Edit:
It's about controls inside controls not controls inside forms.
No, no fancy UI. I know that I can use regions to create crazy desktop apps. That's not what I want.
Thanks!
A Form is a control as well, so it behaves very much like other controls.
The rendering limitation comes from the fact that controls are windows in the operating system which just represent a rectangular area of the display. A window cannot draw anything outside of its own bounds so if a child control is placed outside those bounds, it'll not be rendered (or only partially, depending on its position).
There's no easy way around this and the whole concept of child controls revolves around the parent controlling what gets drawn. If you want to draw a child control outside the bounds of its parent, remove the "child" from the parent, since the relationship doesn't really apply.
You can come up with some custom rendering code that would work around this but then controls wouldn't be true children of other containers and you'd have to write a fair amount of rendering code to get it working. There may be some situations where this would be the desired behavior but in most cases the default system behavior is better (and means less work for the programmer).
You can get a visual effect of a button being "outside" the form by custom rendering the form (that is, drawing it smaller than it actually is) and then rendering the button in the empty space. This, however, is a lot of work and I'm not sure you'd want to take that route.
If you're trying to do a fancy UI where some controls are shaped like a blob (not like a rectangle) or they appear floating (detached from the "main" UI), similar to some media players, etc.: those programs do the entire custom-rendering thing; their windows are still rectangular windows but during rendering they use transparent images and regions to render the form.
It'd help if you added more detail to your question as to why do you want to do this.
You can't draw outside of the controls/parents bounds. However you can place the control in one of the parents ancestors.
This is some code I use to create a drop down menu when this custom control is toggled:
private void Me_ToggledChanged(System.Object sender, System.EventArgs e)
{
if (this.Parent != null) {
Control topParent = this;
int x = 0;
int y = 0;
while (topParent.Parent != null) {
x += topParent.Left;
y += topParent.Top;
topParent = topParent.Parent;
}
if (this.Toggled) {
if (!topParent.Controls.Contains(dropDownMenu)) {
topParent.Controls.Add(dropDownMenu);
}
dropDownMenu.Location = new Point(x, y);
dropDownMenu.Visible = true;
dropDownMenu.BringToFront();
} else {
if (topParent.Controls.Contains(dropDownMenu)) {
this.Parent.Controls.Remove(dropDownMenu);
}
dropDownMenu.Visible = false;
}
}
}
The dropDownMenu is a control that is setup elsewhere in the constructor of this control.
I start by getting the top most parent, as I check each parent I also get a running x and y value of the controls position relative to the parent.
If this control is toggled then I add it to the top most parent and set its position, otherwise I remove it.
I'm sure there are more complex ways and this may not work in all circumstances however this solution work perfect for me writing a .NET CF menu button.
In WinForms it is not possible to draw outside of a control's bounds.
The easiest way to limit a button to the inside of a panel is to use the Anchor property appropriately. Usually buttons are right aligned. If you want to keep this alignment when the panel resizes, dock the button to the right side. If the button is at the bottom anchor it to the bottom as well, otherwise to the top.
If you want the button to resize with the panel width, then dock it to the left and to the right side at the same time and additionally to the top or bottom.
Image found here: Getting Started with Windows Forms: Anchor the Buttons to the Bottom Right.
Related
I start out with a control that has three child controls, docked to the top, bottom, and fill. Two skinny bands at the top and a big rectangle in the middle.
Later on, programatically I want to add a slim band to the left.
But when I do it, I get the left band overlapping with the big middle one.
How should I be readjusting these controls?
Once you have instanciated the new control, you can give it a specific index to be located.
The lower the index, the higher priority it will have.
Example code*:
var control = new Button();
control.Dock = DockStyle.Left;
this.Controls.Add(control);
this.Controls.SetChildIndex(control, 0);
(I just used a button because it was easy to see inside a form.)
However doing this dynamically may not be the best thing. I would recommend creating the control on the left the entire time, then making it visible when applicable. Doing this method, you get the Designer to help place the control in the right spot, rather than generating the right spot.
If you so choose to use the enable/disable visibility at runtime, you may have to tweak the Document Outline. It's located at View -> Other Windows -> Document Outline when in the Designer view. (It's a dockable window like the toolbox.)
You need a control that holds the slim band and a control located on the right. Place the two skinny bands and one rectangle in the right-hand control.
How do I implement scrolling for my Custom Control? My control is fully custom drawn and its height is variable, and a part of the control contains a menu so if there are many items in the control, I'll need to be able to put scroll bars there. I've not really been able to find any clues on how to do this. I did see something about ScrollableControl, but I'm still not sure if that's what I need.
Also, how will my control know when it needs to show the scroll bars? Because my control is fully custom drawn so there's no real "controls" in there it's just a bunch of pixels that are drawn onto it so it's not like I can just set AutoScroll to true and I can't do that anyway because it's not the main part of the control that needs scrolling, it's a particular location on the control that will need to have the scrollbars.
If your custom control inherits from the Panel control, you just set the size of the content yourself in the custom control by this setting:
this.AutoScrollMinSize = New Size(yourWidth, yourHeight);
If your control's ClientSize.Height is greater than yourHeight, you won't get any scrollbars. If it's less, then you get a scrollbar.
In your paint method, add this to the beginning:
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.TranslateTransform(this.AutoScrollPosition.X,
this.AutoScrollPosition.Y);
Now everything you paint gets automatically transformed to the scrolling coordinates.
You have two options.
The good new is that it is possible and both are not really hard.
The bad new is that for one option you will have to adapt all your drawing code:
First make your Control, I use a Panel, to have Autoscroll=true;
Then you need to add one dummy control, I use another Panel, maybe like this, far enough to the right and bottom to force the ScrollBars to show:
public Form1()
{
InitializeComponent();
Panel panelDummy = new Panel();
panelDummy.Size = new Size(1,1);
panelDummy.Location = new Point(yourMaxX,yourMaxY);
panel1.Controls.Add(panelDummy);
}
And then you need to adapt your drawing code. Here is how:
private void panel1_Paint(object sender, PaintEventArgs e)
{
int xx = panel1.HorizontalScroll.Value;
int yy = panel1.VerticalScroll.Value;
e.Graphics.FillRectangle(Brushes.Wheat, new Rectangle(11 - xx, 22 - yy, 22, 311));
e.Graphics.FillRectangle(Brushes.RosyBrown, new Rectangle(11 - xx, 280 - yy, 22, 3));
}
private void panel1_Scroll(object sender, ScrollEventArgs e)
{
panel1.Invalidate();
}
I have added an Invalidate to the Scroll event to avoid messed up painting results.
The other option is simpler: Make your control large enough to hold all your drawn controls and the put it inside a Panel with AutoScroll=true; this will delegate the whole scrolling business to the containing Panel.
you can use aPanel with AutoScroll = true. After that, if you put any controls inside it, as long as their size is larger than the size of panel, the panel will automatically show scroll bars.. The trick can be used for any custom control asa well, as long as you place it inside a AutoScroll panel, and size it as large as you need it to be.
I've partially solved this problem by creating a custom control that inherits from Control and that is completely drawn in OnPaint. My solution so far is to use ScrollBarRenderer to draw my scroll buttons, then I define a Rectangle for the scrollable area. I then create an Image to draw my scrollable content onto, and use TranslateTransform Scroll that Image into position before using DrawImage to draw into the Scrollable Content Rectangle that I created. It's showing promise, although I haven't gotten it completely working yet since I have to handle all of the MouseOver and Click events on my own.
EDIT:
So far all the answers have pointed out the code as originally written was hacky. I didn't write it, and don't understand fully how it all worked. However, in the general case, it did work. I have tried various tidy-ups, but they don't fix the basic problem, because of the strange behaviour of PointToScreen.
An alternative to explaining that behaviour, which I can see is difficult to explain without understanding the inner working of the rest of the form (and I don't at the moment), is to come up with an alternative way to implement the desired functionality, which is a small dialog appearing on top of a button when the button is pressed. The current implementation is a form which appears on top, and then tracks the original form to maintain its position. Is there a cleaner option?
Original Question:
I'm trying to position a form (form A) on top of a control (button B) on a form (form C) using PointToScreen:
Point buttonCorner = buttonB.Parent.PointToScreen(buttonB.Location);
where buttonB is a button in a FlowLayoutPanel on a form.
Most of the time it works, I get the position I expect. I use this code in a LocationChanged event on the form, so that form A tracks button B. This is working fine.
However, when another action causes the parent form to be resized, I find that the position of buttonCorner is offset, such that form A ends up off the screen (to the right). No part of my form is off the screen before this event.
The only theory I can come up with is that the LocationChanged event is firing while the FlowLayoutPanel is re-arranging, and it returns a bad position during that time. It seems a little far-fetched, but I don't have a better theory.
Suppose you have panelA and panelB. You have to register the LocationChanged of the panelB not of the form:
panelB.LocationChanged += (s,e) => {
Point buttonCorner = PanelControlFlow.PointToScreen(PanelControlFlow.Location);
//...
};
Anyway I can feel that your code has wrong approach, your 2 controls may not need to track each other unless you change their locations at runtime. You should provide more details so that we can find another better approach. Note that the FlowLayoutPanel will compact the controls automatically and positioning controls on it won't have any effect.
UPDATE:
For the reason why your PointToScreen returns some off-screen Point. I've tested this, when the form is minimized, its Location is -32000, -32000. All the PointToScreen will be added with this Location, that means if your control has Left calculated on your form is 300, then its Left calculated on the screen will be approximately -32000 + 300 = -31700. The same for Top. That's why your control's Location goes off screen. So you have to register the LocationChanged of the control you want to track to not the form. Why does the minimized window have Location of -32000,-32000? I think that's how a Window is hidden from screen.
Point buttonCorner = PanelControlFlow.PointToScreen(PanelControlFlow.Location);
That code is definitely wrong, the Location value is relative to the control's Parent. If you want to know where the panel's upper-left corner is located then you must use:
Point panelCorner = PanelControlFlow.PointToScreen(Point.Empty);
or for a button in the panel:
Point buttonCorner = someButtonInPanel.Parent.PointToScreen(someButtonInPanel.Location);
Then to get another button on top:
anotherButton.Location = anotherButton.Parent.PointToClient(buttonCorner);
I am trying to overlap panels so that whenever I click a button A certain panel will be visible.
However doing this job is very tricky as because the panels doesn't overlap.
for ex. I have panel 1 and panel 2:
I make panel 2 the same as panel 1,
Whenever I position them on the same position...
Sometimes, the panel 2 becomes a member of panel 1 and whenever I set the visibility of panel 1 to true panel 2 also shows up.
What I want is that the two panels to overlap each other.
"Btw, I'm making a vertical tab that's why I thought that hiding, unhiding the panels might be my best approach.
Is it possible to make the panels overlap each other?
The designer is fighting you do get them overlapped. You need to use a little trick to stop the bottom panel from sucking up the overlapping panel. Put it overlapping panel somewhat off towards the upper-left so they are truly overlapped. Then put it in the right spot by adding code to the form constructor:
public Form1() {
InitializeComponent();
panel2.Location = panel1.Location;
panel2.Size = panel1.Size; // optional
}
Another way to do it is with View + Other Windows + Document Layout. You can drag and drop the inner panel to the outer container (form). You will however have to edit the Location property by hand.
It is absolutely possible to have overlapping panels.
The problem you're facing is that the GUI editor treats your panel as containers (that is true) and as long as you place something (including other panel) within a panel it is "nested" in this container.
To avoid this behavior first place one panel and position/size it appropriately. Then right-click on it and choose "Lock controls". That will lock all current form controls and you will be able to put new controls -- including panels -- directly over them, without any fear that something will be nested or somehow placed inside an existing container.
And of course your controls can overlap -- consider only the order or control creation, that will also define the z-order of them in the form -- controls added later and drawn later and thus are positioned on top of those added earlier.
EDIT: Unfortunately I wasn't completely right with my answer. Locking panels does not prevent them from sucking up the controls places entirely within them. But in case of a partial overlap both containers are created on the same level of deepness, so the problem does not exist in case of overlapping panels, as it was asked in the question.
The Panel has a property called Location, which you can modify to fit your needs. As long as you manage to place your Panel so that it is given the correct parent, you can change the position by altering the Location property later. There's really no need to put design code into the constructor or anything such.
And to place the panel in the correct parent, just have the parent selected and double click the Panel control in the toolbar, rather than dragging it into the form manually. There's really no need to try to fight the designer on this one.
I have a tabcontrol with several tabpages. I also have hooked into the InputPanel.EnabledChanged event so that the tabcontrol resizes when the inputpanel is enabled. What I would like to do is ensure that the control with the focus is visible when the tabpage is resized. What's the best way to accomplish this. Note that I am using the .NET Compact Framework.
Thanks,
MrB
You have a couple of things you need to/may want to keep track of here:
• Current scroll position
• Position of the tapped element (I assume a textbox) when they tapped it
Note that the EnabledChanged event for the InputPanel is pretty easy to deal with (I assume you've already done it): check the InputPanel's Enabled state and do a += or -= to the tabcontrol's Height so the InputPanel doesn't cover anything up.
My understanding is that your issue is really to determine where the textbox was, because your worst-case scenario is that it will be moved off-screen when the tabcontrol resizes.
I believe (not 100% certain) that resizing a tabcontrol will keep the top of the tabcontrol contents visible and you'll have to scroll down to see anything that is now "covered" by the InputPanel.
What you'll want to do in the EnabledChanged event is find out the location of the control that was tapped (or the screen coordinate that was tapped - whatever is easiest for you). If it falls within the danger zone of (ClientSize.Height-InputPanel.Bounds.Height,ClientSize.Height), you'll need to adjust your scroll.
This is half-C#-half-pseudocode but you should be able to see where I'm going with it:
int yOffset = top y-coordinate of the tapped control;
if ( yOffset > ClientSize.Height-inputPanel.Bounds.Height )
{
int yShift = amount to shift everything up;
Point scrollPosition = AutoScrollPosition;
scrollPosition.Y = scrollPosition.Y - yShift;
AutoScrollPosition = scrollPosition;
}
What this does, in a nutshell, is checks if your InputPanel is going to cover up the desired control, and if so, scroll up by yShift pixels, where yShift might be the whole height of the inputPanel, or just enough to barely show your control (whatever you want).
• .NET CF seems very forgiving about setting invalid scroll rules. If you can only scroll up 10 pixels and you tell it to scroll up 100, it will stop at 10.
• Screen coordinate (0,0) is the top left of the visible screen - NOT the form being displayed. You can get tripped up if you assume that (0,0) is the top of your form.
The only way that comes to my mind is calculate this by currrent controls positions on a screen.