I wish to load multiple groupboxes in the windows form application using a button_click event.
A groupbox should appear in the form each time the button is clicked.
Expected output.
I am having trouble making the location of the groupbox dynamic, as the second groupbox should be some distance away from the first groupbox. I thought of manually calculating the coordinates and using an array of points for the location, but I feel that there should be a better a way to go about it.
I have defined 'int count=0' variable to count the number of times the button is clicked. Based on that I am naming the new groupbox. But I think there is some problem in the logic used in the count++ line. It is not going after 1. Therefore I am only getting one groupbox "groupBox1". Nothing happens when I click the button again.
I appreciate your help.
Thank you
int count=0;
private GroupBox GetGroupBox(int a)
{
GroupBox groupBox = new GroupBox();
groupBox.Text = "groupBox"+(a.ToString());
groupBox.Width= 200;
groupBox.Height= 200;
groupBox.Location = new Point(50,400);
return groupBox;
}
private void button1_Click(object sender, EventArgs e)
{
count++;
this.Controls.Add(GetGroupBox(count));
}
Your question states these objectives:
Dynamically add a GroupBox based on an event (like button click).
Assign the new GroupBox location.
Pad the location with "some distance away".
You say you "feel that there should be a better a way to go about it" and there is!
Try experimenting with a FlowLayoutPanel which handles all three of these by its nature.
Here's the code I used to add and remove instances of CustomGroupBox. This is a UserControl that I added to my project, but this will work with any type of control.)
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
numericUpDownGroupboxes.ValueChanged += onGroupBoxCountChanged;
foreach (var radio in Controls.OfType<RadioButton>())
{
radio.CheckedChanged += onFlowLayoutDirectionChanged;
}
}
When the numeric up-down changes, compare the expected number of groupboxes to the current count. Alternatively, you can continue to use a button click and go straight to flowLayoutPanel.Controls.Add(...).
private void onGroupBoxCountChanged(object sender, EventArgs e)
{
// Need an int for comparison.
int
countIs = flowLayoutPanel.Controls.OfType<CustomGroupBox>().Count(),
countShouldBe = (int)numericUpDownGroupboxes.Value;
switch(countIs.CompareTo(countShouldBe))
{
case -1:
flowLayoutPanel.Controls.Add(
new CustomGroupBox
{
Name = $"groupBox{countShouldBe}",
Text = $"GroupBox {countShouldBe}",
Size = new Size(300, 150),
Margin = new Padding(10),
BackColor = Color.White,
});
break;
case 1:
Control last = flowLayoutPanel.Controls.OfType<CustomGroupBox>().Last();
flowLayoutPanel.Controls.Remove(last);
break;
}
}
The direction of the flow can also be specified.
private void onFlowLayoutDirectionChanged(object sender, EventArgs e)
{
if(radioButtonHorizontal.Checked)
{
flowLayoutPanel.FlowDirection = FlowDirection.LeftToRight;
}
else
{
flowLayoutPanel.FlowDirection = FlowDirection.TopDown;
}
}
}
Since you want to create boxes from left to right you should adjust Left: say, 1st box should have Left = 50, 2nd Left = 270, 3d Left = 490 etc.
Code:
const int deltaX = 20;
...
//TODO: check do you really want Top = 400, not, say, 20?
groupBox.Location = new Point(50 + (a - 1) * (groupBox.Width + deltaX), 400);
...
Simplified implementation can be
int count = 0;
// Let's rename the method: we actually create GroupBox, not get existing
private GroupBox CreateGroupBox(int index) => new GroupBox() {
Text = $"groupBox{index}",
Size = new Size(200, 200),
Location = new Point(50 + (index - 1) * (20 + 200), 400),
Parent = this, // Instead of Controls.Add()
};
private void button1_Click(object sender, EventArgs e) {
CreateGroupBox(++count);
}
Related
I want to make a moving label seem nicer and smoother than just reappearing the whole thing to the left after it has all gone out of panel width .For example label 'Hello' , as soon as 'lo' goes out of bounds in the right I want it to reappear on the left. Is there any possible solution to this ?
Here's the code I have for the label now .
private void timer2_Tick(object sender, EventArgs e)
{
label5.Location = new Point(label5.Location.X + 3, label5.Location.Y);
if (label5.Location.X > this.Width)
{
label5.Location = new Point(0 - label5.Width, label5.Location.Y);
}
}
Try this, using a Label (here, named lblMarquee and a System.Windows.Forms.Timer).
The scrolling time is regulated by both the Timer.Interval and a float Field (marqueeStep).
The Timer.Tick event just calls lblMarquee.Invalidate(), causing the Label control to repaint itself.
When the scrolling text, in relation to its current position, goes beyond the limits of the Label.ClientRectangle, the section of the text which is not visible anymore is painted at start of the Label.ClientArea:
System.Windows.Forms.Timer marqueeTimer = new System.Windows.Forms.Timer();
string marqueeText = string.Empty;
float marqueePosition = 0f;
float marqueeStep = 4f;
private void form1_Load(object sender, EventArgs e)
{
marqueeText = lblMarquee.Text;
lblMarquee.Text = string.Empty;
marqueeTimer.Tick += (s, ev) => { this.lblMarquee.Invalidate(); };
marqueeTimer.Interval = 100;
marqueeTimer.Start();
}
private void lblMarquee_Paint(object sender, PaintEventArgs e)
{
var marquee = sender as Label;
SizeF stringSize = e.Graphics.MeasureString(marqueeText, marquee.Font, -1, marqueeFormat);
PointF stringLocation = new PointF(marqueePosition, (marquee.Height - stringSize.Height) / 2);
stringLength = marquee.ClientRectangle.Width - stringLocation.X;
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
e.Graphics.DrawString(marqueeText, marquee.Font, Brushes.Black, stringLocation, marqueeFormat);
if (marqueePosition >= marquee.ClientRectangle.Width) marqueePosition = 0f;
if (stringSize.Width + stringLocation.X > marquee.ClientRectangle.Width) {
PointF partialStringPos = new PointF(-stringLength, (marquee.Height - stringSize.Height) / 2);
e.Graphics.DrawString(marqueeText, marquee.Font, Brushes.Black, partialStringPos, marqueeFormat);
}
marqueePosition += marqueeStep;
}
A couple of other implementations you might find useful:
How to follow the end of a text in a TextBox with no NoWrap
How to draw a string on two not adjacent areas
You need to have two label controls to do this, but it's not really that difficult. First, create a backup label and set it's properties to look like label5:
// A backup label for our scrolling label5
private Label label5_backup;
private void Form1_Load(object sender, EventArgs e)
{
label5.Text = "This is a scrolling label!";
// Set label5_backup to look like label5
label5_backup = new Label
{
Size = label5.Size,
Text = label5.Text,
Top = label5.Top,
Visible = false
};
Controls.Add(label5_backup);
timer2.Interval = 1;
timer2.Start();
}
Then, in the Tick event, as soon as our label5 starts to leave the client rectangle, set our backup label to the proper distance from the left of the form so that it starts to appear on the other side. And as soon as label5 is completely off the form, set it's location to match the backup label and then hide the backup label again.
Note that you can just set the Left property instead of creating a new Location point each time, which simplifies the code a little:
private void timer2_Tick(object sender, EventArgs e)
{
label5.Left++;
// If label5 starts to go off the right, show our backup on the left side of the form
if (label5.Right > ClientRectangle.Width)
{
label5_backup.Left = label5.Right - ClientRectangle.Width - label5.Width;
label5_backup.Visible = true;
}
// If label5 is all the way off the form now, set it's location to match the backup
if (label5.Left > ClientRectangle.Width)
{
label5.Location = label5_backup.Location;
label5_backup.Visible = false;
}
}
Also, if you want to make the scrolling smoother, only increment the Left by 1 each time and reduce the timer2.Interval to a third of what it was before (unless it's already at 1).
hello everyone i'm new to c# and wpf programming and
i'm trying to create a dynamic menu where i have + and - buttons which affect a text box which represents quantity. so in a grid i call a class called productcard which i call in a page to fill the grid with the products.
now the problem is how can i use the click event inside of the product card class in my page where i have multiple cards.
class productcard
{
Button plus = new Button();
Button minus= new Button();
public TextBox qtyl = new TextBox();
Grid z = new Grid();
public int left;
public int top;
GroupBox yy;
public GroupBox XX { get { return this.yy; } set { this.yy = value; } }
public productcard(int left , int top )
{
this.left = left;
this.top = top;
Thickness margin = new Thickness(left, top, 0, 0);
Thickness bmar = new Thickness(0, 0, 0, 0);
plus.Height = 30;
plus.Width = 40;
plus.VerticalAlignment = VerticalAlignment.Bottom;
plus.HorizontalAlignment = HorizontalAlignment.Right;
plus.Content = "+";
plus.HorizontalContentAlignment = HorizontalAlignment.Center;
// - button
minus.Height = 30;
minus.Width = 40;
minus.VerticalAlignment = VerticalAlignment.Bottom;
minus.HorizontalAlignment = HorizontalAlignment.Left;
minus.Content = "-";
minus.HorizontalContentAlignment = HorizontalAlignment.Center;
// add the button to the grid
z.Children.Add(plus);
z.Children.Add(minus);
// creat text box
qtyl = new TextBox();
qtyl.Height = 30;
qtyl.Width = 30;
qtyl.Background = Brushes.White;
qtyl.VerticalAlignment = VerticalAlignment.Bottom;
qtyl.HorizontalAlignment = HorizontalAlignment.Center;
qtyl.Text = "0";
// add text box to the grid inside the group box
z.Children.Add(qtyl);
// creat group box
GroupBox yy = new GroupBox();
yy.Margin = margin;
yy.VerticalAlignment = VerticalAlignment.Top;
yy.HorizontalAlignment = HorizontalAlignment.Left;
yy.Content = z;
yy.Height = 150;
yy.Width = 150;
XX = yy;
// insert group box in the produc grid
}
public void plus_Click(object sender, EventArgs e)
{
// this.plus.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
MessageBox.Show(" + has been cliked");
int result=Convert.ToInt32(qtyl.Text)+1;
qtyl.Text = result.ToString();
}
private void minus_Click(object sender, EventArgs e)
{
int result = Convert.ToInt32(qtyl.Text) - 1;
qtyl.Text = result.ToString();
}
}
You can make a handler for your button like this:
Button myButton=new Button();
myButton.Click += delegate(object sender, RoutedEventArgs e) {
//handle event
};
I hope this helps.
Reza is correct in how to write more code for a Button generated in code.
However, I would give you a word of warning that this is not proper WPF usage of MVVM and you might be putting yourself down a path for trouble later.
I would suggest having your view's buttons bind to an ICommand that can be defined in a ViewModel that will handle doing the logic for your text update.
As you mentioned, you're having different view controls represent the data based on your button press. You're currently surviving as the view's are directly updating each other (THIS IS BAD).
The moment you want to represent this data in other views, say you want your button to update 5 labels in 3 different layouts in 2 windows, you're going to have unmaintainable references in your views.
If you have the ViewModel get a command from your view bound to the button, you can have the command logic update the property in the ViewModel that multiple views can be bound to and update them all at the same time via INotifyPropertyChanged.
Not to mention, ICommand can also let you disable buttons cleanly from being clicked.
Consider taking an hour to check out this tutorial to see the separation of View and ViewModel. What you're doing now looks like it's setting you up for a world of hurt later...
I have a form with a DataGridView on it. I select a row (always a single row) of data. After I click a button I would like to open a new form and always position it directly under the selected row. How should I do this?
In my button click I do something like this:
private void myBtn_Click(object sender, EventArgs e)
{
if (myDGV.SelectedCells.Count > 0)
{
int i = myDGV.SelectedCells[0].RowIndex;
DataGridViewRow r = myDGV.Rows[i];
// READ SOME DATA FROM THE ROW
newForm form = new newForm();
//POPULATE THE NEW FORM WITH DATA
form.ShowDialog(this); //POSITION OF THIS FORM SHOULD BE DIRECTLY UNDER SELECTED ROW
}
}
You can use DataGridView.GetRowDisplayRectangle to obtain the row rectangle in data grid view client coordinates, then Control.RectangleToScreen to convert them to a screen coordinates needed for determining the new form location, like this:
private void myBtn_Click(object sender, EventArgs e)
{
if (myDGV.SelectedCells.Count > 0)
{
int i = myDGV.SelectedCells[0].RowIndex;
DataGridViewRow r = myDGV.Rows[i];
// READ SOME DATA FROM THE ROW
newForm form = new newForm();
//POPULATE THE NEW FORM WITH DATA ...
// Position the form under the selected row
form.StartPosition = FormStartPosition.Manual;
var rect = myDGV.RectangleToScreen(myDGV.GetRowDisplayRectangle(i, false));
form.Location = new Point(rect.Left, rect.Bottom);
form.ShowDialog(this);
}
}
While Ivans solution is 100% valid and correct, there are some minor "gripes" with it as far as aesthetics are concerned (which he could easily cater for in his elegant solution). If you want the dialog box to appear directly underneath the row, flush with both the left side of the gridview and the row bottom, you need to do it differently - the long way.
I reiterate this - Ivans solution is very appropriate and much cleaner.. The below solution gives you a little more freedom concerning form placement and alike. I threw this together in a few minutes so sorry if I missed something small.
You can always use the built in properties like Rectangle.Bottom and alike. I did this to, I guess, show the math on how to approach things.
private void myBtn_Click(object sender, EventArgs e)
{
if(dataGridView1.SelectedRows.Count > 0)
{
var rowIndex = myDGV.SelectedRows[0].Index;
var row = myDGV.Rows[rowIndex];
var formLocation = this.Location; //Form location
var gridLocation = myDGV.Location; //grid location
var rowLocation = myDGV.GetRowDisplayRectangle(rowIndex, false).Location; //row location
newForm form = new newForm();
form.StartPosition = FormStartPosition.Manual; //set to manual
//form.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
//form.BackColor = Color.Blue;
form.Location = GetPopupStartingLocation(
new Point[]
{
formLocation,
gridLocation,
rowLocation
},
row.Height);
form.Show(this);
}
}
/*
A small helper method - didn't need to put this in a method
but I did it for clarity.
*/
private Point GetPopupStartingLocation(Point[] locations, int rowHeight)
{
var yPadding = 5;
var xValues = locations.Sum(x => x.X);
var yValues = locations.Sum(x => x.Y) + ((rowHeight * 2) + yPadding);
return new Point(xValues, yValues);
}
Now Most of this is pretty much the same as Ivan's except the "longer route" but this gives you a little more control over your location using the helper function. You can add/remove adjust the values down there and find something you like. Works fine.
Hope this helps.
+1 to Ivan for the clean solution.
I've been trying to solve my issue for quite a while and to be honest am getting nowhere. What i would like is when the user clicks the 'top' button on my panel it automatically goes to the top( and swaps with the one there.) and when they click the bottom button it automatically goes to the bottom. I'm setting the index panel manually but of course this doesnt work because its only viable for one panel (i have ten). Greatly appreciate some help in finding a method that can send the panel to the top of the stack regardless of its position.
Here is a image (basic) to help understand
Control ctrlToMove = (Control)this.bookControls[bookName];
int ctrlToMoveIndex = bookPanel.Controls.IndexOf(ctrlToMove);
int ctrlToSwapIndex = ctrlToMoveIndex - 5;
Control ctrlToSwap = bookPanel.Controls[ctrlToSwapIndex];
this.bookPanel.Controls.SetChildIndex(ctrlToMove, ctrlToSwapIndex);
this.bookPanel.Controls.SetChildIndex(ctrlToSwap, ctrlToMoveIndex);
Based on your drawing, I made a UserControl with a button on it:
void uc_ButtonClicked(object sender, EventArgs e) {
UserControl1 uc = sender as UserControl1;
if (uc != null) {
int childIndex = flowLayoutPanel1.Controls.GetChildIndex(uc);
if (childIndex > 0) {
UserControl1 ucTop = flowLayoutPanel1.Controls[0] as UserControl1;
flowLayoutPanel1.Controls.SetChildIndex(uc, 0);
flowLayoutPanel1.Controls.SetChildIndex(ucTop, childIndex);
}
}
}
According to your picture you have one control per row in panel. Thus I suggest you to use TableLayoutPanel instead of FlowLayoutPanel. Also I'd create user control for items in panel. E.g. it will have name PriorityUserControl and four buttons to increase, decrease, maximize, minimize it's 'priority' (I placed buttons horizontally just to save place on screen):
Next, create four events in this user control:
public event EventHandler PriorityMaximized;
public event EventHandler PriorityIncreased;
public event EventHandler PriorityDecreased;
public event EventHandler PriorityMinimized;
And rise appropriate event when button clicked:
private void topButton_Click(object sender, EventArgs e)
{
if (PriorityMaximized != null)
PriorityMaximized(this, EventArgs.Empty);
}
That's it. We have user control which tells whether it want to move up or down. Now add user controls to TableLayoutPanel (either manually or dynamically) and subscribe same event handlers of these four events to ALL user controls. Something like:
// create user control and attach event handlers
PriorityUserControl control = new PriorityUserControl();
control.PriorityMaximized += priorityUserControl_PriorityMaximized;
control.PriorityMinimized += priorityUserControl_PriorityMinimized;
control.PriorityIncreased += priorityUserControl_PriorityIncreased;
control.PriorityDecreased += priorityUserControl_PriorityDecreased;
// add another row to table
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
panel.RowCount = panel.RowStyles.Count;
// add control table layout panel
panel.Controls.Add(control);
panel.SetRow(control, panel.RowCount - 1);
Good. All you should do now is implement these event handlers. It's simple. E.g. decreasing priority (i.e. moving down):
private void priorityUserControl_PriorityDecreased(object sender, EventArgs e)
{
// sender is a control where you clicked Down button
Control currentControl = (Control)sender;
// get position in panel
var position = panel.GetPositionFromControl(currentControl);
// just to be sure control is not one at the bottom
if (position.Row == panel.RowCount - 1)
return;
// we want to switch with control beneath current
Control controlToSwitch = panel.GetControlFromPosition(0, position.Row + 1);
// move both controls
panel.SetRow(currentControl, position.Row + 1);
panel.SetRow(controlToSwitch, position.Row);
}
Now implementation of maximizing priority (i.e. moving to top):
private void priorityUserControl_PriorityMaximized(object sender, EventArgs e)
{
Control currentControl = (Control)sender;
var position = panel.GetPositionFromControl(currentControl);
if (position.Row == 0 || panel.RowCount < 2)
return;
Control topControl = panel.GetControlFromPosition(0, 0);
panel.SetRow(currentControl, 0);
panel.SetRow(topControl, position.Row);
}
I believe you will create rest two handlers by yourself.
The key of what you want is setting up a clear and extendable algorithm capable to deal with the different positions of the Panels. Here you have a simple code showing certain approach to this problem:
public partial class Form1 : Form
{
int[] panelLocations;
Point[] pointLocations;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
panelLocations = new int[5];
pointLocations = new Point[5];
panelLocations[1] = 1;
panelLocations[2] = 2;
panelLocations[3] = 3;
pointLocations[1] = new Point(panel1.Left, panel1.Top);
pointLocations[2] = new Point(panel2.Left, panel2.Top);
pointLocations[3] = new Point(panel3.Left, panel3.Top);
}
private void relocate(int curPanel, bool goTop)
{
int curLoc = panelLocations[curPanel];
int newLoc = curLoc - 1;
if (!goTop)
{
newLoc = curLoc + 1;
}
if (newLoc < 1) newLoc = 3;
if (newLoc > 3) newLoc = 1;
if (newLoc != curLoc)
{
int otherIndex = Array.IndexOf(panelLocations, newLoc);
panelLocations[curPanel] = newLoc;
relocatePanel(curPanel);
panelLocations[otherIndex] = curLoc;
relocatePanel(otherIndex);
}
}
private void relocatePanel(int curIndex)
{
if (curIndex == 1)
{
panel1.Location = pointLocations[panelLocations[1]];
}
else if (curIndex == 2)
{
panel2.Location = pointLocations[panelLocations[2]];
}
else if (curIndex == 3)
{
panel3.Location = pointLocations[panelLocations[3]];
}
}
private void buttonTop1_Click(object sender, EventArgs e)
{
relocate(1, true);
}
private void buttonBottom1_Click(object sender, EventArgs e)
{
relocate(1, false);
}
}
Open a new project, add 3 panels (Panel1, Panel2 and Panel3... better put different background colors) and include two buttons (buttonUp and buttonDown). This code will make the Panel1 to go up and down (by changing its position with the other panels).
The idea is pretty simple: at the start you store the positions of all the Panels in an array. In another array, you store where each panel is located every time (1 is the original position of Panel1, etc.).
It is a quite simple code which you can improve and extend as much as required, but the idea is pretty reliable and you can use it in any case: a set of fixed positions through which the panels will be moving.
I'm working on some project ... and when I add a user there is an information about hobby with the advantages of support -add more- hobby to the same person .
I'm thinking about divided my info into several user control and locate these user control dynamically inside the panel .
and when press the -add more- link it's build a new controls (label , textbox ...) and resize the user control containing them .
it's work but the problem is when I press -add more- the user control containing it resize well . but the panel did not build again so the user control get above other user controls without rearrange .
this is simple code of user control with add more :
public partial class UserControl2 : UserControl
{
public UserControl2()
{
InitializeComponent();
}
private void UserControl2_Load(object sender, EventArgs e)
{
this.Size = new Size(this.Size.Width , sss.Size.Height * 3);
}
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
this.Size = new Size(this.Size.Width, sss.Size.Height * 6);
}
}
and the panel code is to locate when press button1 :
private void button1_Click(object sender, EventArgs e)
{
UserControl1 x1 = new UserControl1();
UserControl2 x2 = new UserControl2();
UserControl3 x3 = new UserControl3();
x1.Location = new Point(panel1.AutoScrollPosition.X , panel1.AutoScrollPosition.Y);
x2.Location = new Point(panel1.AutoScrollPosition.X , panel1.AutoScrollPosition.Y + x1.Size.Height);
x3.Location = new Point(panel1.AutoScrollPosition.X, panel1.AutoScrollPosition.Y + x1.Size.Height + x2.Size.Height);
panel1.Controls.Add(x1);
panel1.Controls.Add(x2);
panel1.Controls.Add(x3);
}
You can't increase the size of a control without it overlapping if you aren't anchoring the controls and increasing the size of the window.
While the form designer may look like it flows nicely, everything is created with fixed locations and sizes. If you start increasing the size of a control without taking the other controls into account, it will be drawn over them.
You should look into either anchoring your controls and increasing the window size, within reason, or instead of changing the underlying size, add the controls inside the panel and the needed location and enable auto scrolling for the panel.
This is example code:
private void button1_Click(object sender, EventArgs e)
{
if (!panel1.AutoScroll) panel1.AutoScroll = true;
for (int i = 0; i < 3; i++)
{
Textbox txt = new TextBox() { Location = new Point(3, (panel1.Controls.Count * 25) + 3 };
panel1.Controls.Add(txt);
}
}
Each time you click the button it will add 3 new textbox to the panel. You can use a similar pattern to this to accomplish what you are looking for with your own control. It also makes sure the panel can scroll so the users can go down the list without resizing the panel or the main form.
Try:
check out this link:
private void button1_Click(object sender, EventArgs e)
{
panel1.Controls.Clear();
//Then add your existing code below
serControl1 x1 = new UserControl1();
UserControl2 x2 = new UserControl2();
UserControl3 x3 = new UserControl3();
x1.Location = new Point(panel1.AutoScrollPosition.X , panel1.AutoScrollPosition.Y);
x2.Location = new Point(panel1.AutoScrollPosition.X , panel1.AutoScrollPosition.Y + x1.Size.Height);
x3.Location = new Point(panel1.AutoScrollPosition.X, panel1.AutoScrollPosition.Y + x1.Size.Height + x2.Size.Height);
panel1.Controls.Add(x1);
panel1.Controls.Add(x2);
panel1.Controls.Add(x3);
}