I've a base form class designed like that
At the bottom I put a docked-bottom panel and inside this panel I put two buttons. Also I set Anchor's property of these buttons to "Top, Right"
Also I've set Form's AutoScaleMode to None
On my application I inherited this base form and I don't know what is happening. My buttons go to the middle of my panel. If I set it again to the left and maybe rebuild my BaseForm it goes to the middle again.
I've seen this happen when controls in a base form have the protected modifier. That means that an inherited form can set the controls' properties (like Location) itself. BUt why it does it this way, I don't know.
When Winforms turned into an old lady, I stopped fighting her idiosyncrasies, just tried to work around them (as you do with old ladies). In your case I would use a FlowLayoutPanel in stead of a regular Panel and set its FlowDirection = FlowDirection.RightToLeft.
Have you tried checking to make sure in the panel_resize event there isn't any code that manually relocates buttons?
In some of the code I inherited, there is a lot of this :
panel_resize(object sender, EventArgs e)
{
int buttonWidth = btnSubmit.Width + btnCancel.Width + 5; // 5 is buffer between
int leftOffset = (panel.Width - buttonWidth ) / 2 ;
btnSubmit.Left = leftOffset;
btnCancel.Left = btnSubmit.Right + 5; // Buffer
}
This manually centers buttons during resize event ( you can do this differently, but that's what they did.) And means the design time looks different than the actual run time.
Related
I want to have an image partially overlaying a winform control (in this case a datagridview) but it seems that's not working as i would.
I followed the answer found here and it works fine when the parent is the control (DGV) but not when the parent is the form...
I will like to have something like this:
But instead i get something like this :
Following the answer and setting the parent to DGV works fine....
You can use two PictureBoxes and bring one below the DGV and nest the other. Then move the overlay to the right place..
both should be identical otherwise, i.e. have the same Image and the same SizeMode.
Here is a function that'll do it:
void overlayCtls(Control ctlBase, Control ctlOverlay, Control ctlTgt )
{
ctlOverlay.BackColor = Color.Transparent;
ctlOverlay.Parent = ctlTgt;
ctlOverlay.Location = new Point(ctlBase.Left - ctlTgt.Left, ctlBase.Top - ctlTgt.Top);
}
And the result:
Notes:
You explictily need to do the nesting as a DGV is not a container, so it will not be enough to move it in place in the designer.
You explictily need to set the BackColor to Transparent even if it was set in the designer. Looks like it will be taken from the parent unless set in code.
The nested child control will overlay not only the ClientArea of its Parent but also any Border.
Let's say that I have a panel with like... 3 controls in it. I may end up adding more controls to it or changing the positioning within that panel. When the program starts, I will programmatically HIDE the control. Eventually, the user can click a button that will create a duplicate of the original panel to populate an area on the form. The button should have the option for another click eventually, meaning that multiple instances of these can come about to populate this area. Remember that these controls may have text labels within them that can be individually set or altered later on, programmatically. I am assuming that to do this program, I need to make a List of controls, maybe a List of panels? I'm not exactly sure how to do this considering the fact that I need multiple controls duplicated multiple times.
Is there a nice, simple way to do this? I really don't want to do the duplication with any kind of 3rd-party package.
You will have to do it in code and therefore it'll be as nice as you can code ;-)
Seriously the course most often taken is to
create a UserControl which is a class related to a form, with the layout you want..
..and add more and more instances of it..
..often to a FlowLayoutPanel, often with AutoScroll
This is pretty nice and simple imo.
Here is a short walk-though..:
first we start, as usual, by picking a nice name for the UserObject class, maybe 'DataPanel' or 'BookItem'..
Next we create it: Go to the project explorer and right-click, choosing Add-New UserControl and give it the class name you chose. I'll use 'BookItem'.
Now you can see the Designer showing you a small empty control.
Look closer: You can also see that in the project explorer ther is now not only the new 'BookItem.cs' file but also the complementary 'BookItem.Designer.cs' and even a 'BookItem.resx' file; so this works very much like creating a new Form..
Let's add a few controls from the toolbox, I chose to add a PictureBox, four Labels and a NumericUpDown.
Have a look at the BookItem.Designer.cs file: Here you can see the very things you see in a Form.Desginer.cs file: All settings and all declarations for all controls you add to the layout. Note especially the declarations (at the bottom of the file): Just like for a Form, all controls by default are declared as private!
We can now work on the layout and script the controls. We also can add functions and properties to the UC, just like a Form.
Please note: Anything you need to access from outside, read from your form or its methods must be public! So if you want to access the NUpDown, let call it 'nud_quantity' you have a choice
You can change its declaration in the BookItem.Designer.cs from private to public or in the Designer by changing the Modifiers property
Or you can write a public function in the UC to get/set its value
Chosing between those two ways is a matter of taste; if other developers will work with the UC class, it will probably be better to put close control over what you expose by writing access methods.
After you have compiled the project you can see the new UC in the Toolbox.
You can now either add it from the Toolbox or
you can add it in code like any control you create dynamically.
Let's look at an example:
Imagine a simple order system in a bookstore: The customer has done a search on the books in our store and is presented with a list of books in a DataGridView 'dgv_bookList', readonly, multiselect. To the right there is a FlowLayoutPanel 'flp_cart' represeting a shopping cart. And we have a command button 'cb_addItems' to add selected books to the cart.
The Button might be scripted like this:
private void cb_addItems_Click(object sender, EventArgs e)
{
if (dgv_bookList.SelectedRows.Count <= 0) return;
foreach (DataGridViewRow row in dgv_bookList.SelectedRows)
{
BookItem book = new BookItem (row);
book.label1.Text = "#00" + book.label1.Text;
book.Name = book.label1.Text;
flp_cart.Controls.Add(book);
}
}
This will add one BookItem for each selected row in the DGV.
A few things to note on the above code:
I pass a DataGridViewRow into the constructor of the UC so it can directly set its labels! This means that, in addition to the parameterless contructor the desginer has built for us, we need to write a second contructor, maybe like this:
public bookItem()
{
InitializeComponent();
}
public bookItem(DataGridViewRow bookData)
{
InitializeComponent();
label1.Text = bookData.Cells[0].FormattedValue.ToString();
label2.Text = bookData.Cells[1].FormattedValue.ToString();
label3.Text = bookData.Cells[2].FormattedValue.ToString();
label4.Text = bookData.Cells[3].FormattedValue.ToString();
}
Instead you could write a public setData(DataGridViewRow bookData) function.
Also note how stupid my labels are named! You can do better than that, I hope!
Also note how I access 'label1' and modify its Text from a Button in the Form; to do that I had to change its declaration in the Desginer.cs file:
private System.Windows.Forms.PictureBox pb_cover;
public System.Windows.Forms.Label label1; // <<----expose this label !
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.NumericUpDown numericUpDown1;
Often preferrable: An access function, maybe like this:
public int quantity() { return (int) numericUpDown1.Value; }
Or, of course a Property:
public int quantity { get { return (int)numericUpDown1.Value; } }
Also note, that I set the Name of the BookData item to some variant of the 1st data item, my book id. This might as well, or better, happen in the constructor; and there should be a check to prevent adding the same item twice..
All in all one can say, that using UserControls is very much like working with Forms, including all the usual ways or tricks for inter-form communication: keep references, expose members, create properties and functions..
One final Note: Like with forms or subclassed controls there is one catch: By placing them in the designer, you assign the designer the responsiblity to display your UC during design time.
This is normally just fine; however it is also possible to introduce subtle mistakes which make it impossible for the designer to display the control. You need to correct these problems before the designer will be able to show a control or any form that contains it. Let have a look at a simple example of such a problem:
Let's script the Paint event of the PictureBox 'pb_cover' in the UC:
public Brush myBrush = null;
private void pb_cover_Paint(object sender, PaintEventArgs e)
{
if (pb_cover.Image == null)
{
Size s = pb_cover.ClientSize;
e.Graphics.FillRectangle(myBrush, 0, 0, s.Width, s.Height);
e.Graphics.DrawLine(Pens.Red, 0, 0, s.Width, s.Height);
e.Graphics.DrawLine(Pens.Red, s.Height, 0, 0, s.Width);
}
}
And let's modify the code in the Add button:
BookItem book = new BookItem (row);
book.label1.Text = "#00" + book.label1.Text;
book.myBrush = Brushes.OliveDrab;
flp_cart.Controls.Add(book);
Now, if you run the program all will be fine. Even if you try to look at the UC in the designer there may or may not be problems. But once you try to open a Form on which the UC was placed, the Desginer will crash and tell you that it can't work, since the Brush is null. Here the remedy is simple: add a default value to the Brush declaration and all is well. Other situations may need a little more thinking..
I don't even run into the problem btw, since I have not placed an instance of BookItem on the Form; they are only created in the Add Button..
I hope that gets you started!
I have program with two tabs in a TabController, I also have a panel I want to always have in front. Despite what tabpage I am in. I tried setting the panel to BringToFront(), but that donĀ“t seem to work when I change tabpage. Any suggestions how to solve this?
If the Panel is contained by the TabPage, then you'd have to manually switch it to the current tab whenever the tab changes, then call BringToFront().
An alternative would be to make the Panel so it's directly contained by the Form, but in front of the TabControl (like it's "floating" over it). Then it would just stay there. You'd have to either manually fiddle with the Panel's Location() property to get it right (you couldn't drag it over the TabPage as then it would drop into it), or you could position it properly via code in the Load() event of the Form.
Edit:
For instance, if you properly positioned "panel1" in the TabPage at design-time, you could switch it to the Form using code like this:
private void Form1_Load(object sender, EventArgs e)
{
Point pt = panel1.PointToScreen(new Point(0, 0));
panel1.Parent = this;
panel1.Location = this.PointToClient(pt);
panel1.BringToFront();
}
Set the panel outside the tabcontroller and set it's dockstyle. Also set the dockstyle of the tabcontroller.
Panel belongs to a specific tabpage. When you change to another tabpage and call BringToFront(), it doesn't make anything, because you're on another tabpage right now. So you need to workaround that with righting some code. There are two ways:
1) You can place that panel on every tabpage in design time (if you don't need some shared data on that panel).
2) You should hook OnTabPageChanged event and move panel from old tabpage to the page you switched to (if you do need some shared data on that panel). I think this is your case.
I have created a custom UserControl and then another control that is a subclass of this Usercontrol, but still has its own designer file behind it (the base control is just used for some base functionality... I don't need to 'design' anything on it and actually its height is set to 0).
Everything works as intended at run time, but at design time, it seems like whenever I open the child control in Design mode to work on it, Visual Studio keeps increasing the width on me, by perhaps a couple hundred pixels at a time (or possibly some factor). Before I know it, the thing is 10,000 plus pixels wide and then I have to reset the width in the designer just so it is manageable. This isn't an issue at run time, because the controls width gets set by a parent container it is put in, and the control is anchored to the parent. Its just a pain while designing the controls. Here's just little snippets to better explain what I've done:
public partial class BaseRow : UserControl
{
public BaseRow()
{
InitializeComponent();
...
}
}
Then I want to actually create controls that inherit from this BaseRow, but that I can edit in the designer. So, I go to Add -> UserControl. Then in the class code I change the default created code to inherit from my BaseRow (which inherits from UserControl) so it looks like this:
// changed the inheritance below from UserControl to BaseRow (which inherits from UserControl)
public partial class UnitRow : BaseRow
{
public UnitRow()
{
InitializeComponent();
}
}
I originally set the width of the objects to 400 in both the base class and the child class in the designer. The base Row sets its Anchor property in its constructor:
this.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
because these controls will all go in a subclassed panel control that will control the width of rows that are added to it.
Everything works fine at run time. The actual width of these 'Row' controls I have created is set correctly and changes appropriately based on its parent subclassed panel control. But when I open the row controls in design mode at design time, the width is constantly getting longer and longer. Everytime the width equal 10,000 or more I manually change it back in the Properties window to a width of 400.
Its not causing any major issue. But it is a real pain. Anyone have any suggestions?
The workaround is to set anchor in the control's Load event handler:
private void BaseRow_Load(object sender, EventArgs e)
{
if ( !DesignMode )
{
this.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
}
}
Now when you drag&drop control it won't set anchor at design time but will work in runtime.
Okay, I was able to create a simple Windows Forms project that reproduces some strange behavior I found. In the designer, make a form with a ListBox (named lbx) anchored Top, Left, Right, and Bottom, and a button (button1). Now, the Form's code is here:
using System;
using System.Windows.Forms;
namespace ListBoxKaboom
{
public partial class Form1 : Form
{
private bool _initFinished = false;
public Form1()
{
InitializeComponent();
this._initFinished = true;
this.Height += 100;
this.Height -= 50;
this.Height += 50;
}
private void lbx_SelectedIndexChanged(object sender, EventArgs e)
{
this.button1.Enabled = (this.lbx.SelectedItem != null);
}
protected override void OnLayout(LayoutEventArgs e)
{
if (_initFinished)
{
int lines = (this.lbx.Height - 4) / this.lbx.ItemHeight;
this.SuspendLayout();
while (lines < this.lbx.Items.Count)
{
this.lbx.Items.RemoveAt(this.lbx.Items.Count - 1);
}
while (lines > this.lbx.Items.Count)
{
this.lbx.Items.Add("Item " + (this.lbx.Items.Count + 1).ToString());
}
this.ResumeLayout();
}
base.OnLayout(e);
}
}
}
PLEASE NOTE THE FOLLOWING INSTRUCTIONS:
Run this, click any of the items in the list box, and use the arrow keys to move down far enough to cause the list box to scroll. Kaboom.
Exception (sometimes NullReferenceException and sometimes IndexOutOfBoundsException). Any ideas why? Also, I would think that the items would be in order, but they're not. Is this just a goofy corner case that didn't get handled properly by Windows Forms, or am I doing something wrong?
Stack trace:
at System.Windows.Forms.ListBox.NativeUpdateSelection()
at System.Windows.Forms.ListBox.SelectedObjectCollection.EnsureUpToDate()
at System.Windows.Forms.ListBox.SelectedObjectCollection.get_InnerArray()
at System.Windows.Forms.ListBox.SelectedObjectCollection.get_Item(Int32 index)
at System.Windows.Forms.ListBox.get_SelectedItem()
I Copy/Pasted it to an empty Form and get a StackOverflow exception. Looking at it, with manipulation of the Items inside a Layout event I would say you deserve little better.
I realize this may be a simplification of something else, but there simply are limits to what you can do in a EDS.
My best guess: The ResumeLayout triggers a recursive layout operation. You could try to stem it with a sibling to _initFinished but I would suggest rethinking tour design here.
I copy/pasted wrong, my bad (used layout event).
Second try:
based on the two while-loops I would expect the Item strings to be in order, and no vertical scrollbar. It is clear that the listbox is confused, showing vertical scroll range and with the items out-of-order. So some 'error' is already present in the internals of the Listbox, waiting for a Scroll. I can also reproduce it with the mouse.
A workaround: You should be able to get the desired effect using the Resize event.
Attempt to an explanation: The (unmanaged part of the) Listbox gets confused by (multiple) Add/RemoveAt operations with suspended Layout. The last items are drawn at the wrong place, and the Listbox can't compute pixel-to-item.
You should not manipulate any GUI elements in the
constructor, e.g. this.Height += 100; in your example.
Strange things can happen. I have been bitten by this
several times in legacy code.
Wait until form load time - handle the base.Load event and do the
height manipulation there.
From "When does Form.Load event get raised?":
Q: "... I need to find out basically
what the difference is between
putting code in Load event's handler,
versus putting code in the Form's
constructor after the
InitializeComponents() line ..."
A: "The Load event is fired once the
control/form has been fully
initialized and has a window handle
created. Therefore once this event
has fired it is a fully usable user
interface control. Remember that
inside the constructor the actual
window handle for the control/form has
not yet been created, you are only
creating C# objects here and inside
the InitializeComponent call."