Remove controls of flowlayoutpanel and recreating it in C# - c#

I had been experimenting on writing a code to generate images inside a FlowLayoutPanel.
This is what i had done so far, when i click on a button for the first time (by using a checkboxes - read in number of images to open), it will generate the images, when i click on the button on second try, it will not update the flowlayoutpanel.
Even though i tried to remove the controls inside the FlowLayoutPanel, it still doesn't show the second try of the images.
This is the code snippet of the method:
FlowLayoutPanel fwPanel = null;
private void btnOpenFile_Click(object sender, EventArgs e)
{
//if there is content inside the flowpanel, dump it
if (fwPanel != null)
{
listOfFile.Clear();
}
//create a new FLP
fwPanel = new FlowLayoutPanel();
int panelWidth = width * 4 + 50;
int panelHeight = height * 4 + 50;
fwPanel.Size = new Size(panelWidth, panelHeight);
fwPanel.Location = new Point(0, 0);
this.Controls.Add(fwPanel);
//each checked item would be stored into an arraylist
foreach(object itemChecked in clbFile.CheckedItems)
{
listOfFile.Add((clbFile.Items.IndexOf(itemChecked)+1).ToString());
}
int noOfCheckedFile = listOfFile.Count;
PictureBox[] listOfPicture = new PictureBox[noOfCheckedFile];
int positionX = 0, positionY = 0;
int maxPaddingX = (width * MATRIX_SIZE) - 1;
int maxPaddingY = (height * MATRIX_SIZE) - 1;
//dynamically create images.
for (int i = 0; i < noOfCheckedFile; i++)
{
listOfPicture[i] = new PictureBox();
listOfPicture[i].Image = resizeImage((Image)show_picture(Convert.ToInt32(listOfFile[i])), new Size(width, height));
listOfPicture[i].Size = new Size(width, height);
if (positionX > maxPaddingX)
{
positionX = 0;
positionY += height;
}
if (positionY > maxPaddingY)
{
positionY = 0;
}
listOfPicture[i].Location = new Point(positionX,positionY);
listOfPicture[i].Visible = true;
fwPanel.Controls.Add(listOfPicture[i]);
positionX += width;
}
}
show_picture is a method that takes in and integer and returns a bitmap image.
listOfFile is to trace which file to return.
listOfPicture is to store each images.
i tried replacing this lines
//if there is content inside the flowpanel, dump it
if (fwPanel != null)
{
listOfFile.Clear();
}
i have added this line into it, when i do a second click, everything just gone missing, but this is not what i want, because it does not re-populating the FlowLayoutPanel.
if (fwPanel != null)
{
fwPanel.SuspendLayout();
if (fwPanel.Controls.Count > 0)
{
for (int i = (fwPanel.Controls.Count - 1); i >= 0; i--)
{
Control c = fwPanel.Controls[i];
c.Dispose();
}
GC.Collect();
}
fwPanel.ResumeLayout();
listOfFile.Clear();
}
I also tried this, but on second click, nothing will happen.
if (fwPanel != null)
{
List<Control> listControls = fwPanel.Controls.Cast<Control>().ToList();
foreach (Control control in listControls)
{
fwPanel.Controls.Remove(control);
control.Dispose();
}
listOfFile.Clear();
}
I wonder if i miss out anything, can someone enlighten me on what did i miss out ? Or guide me for the best practice of doing this.

as Suggested, i shifted the creation outside (credit to Sinatr for spotting it)
FlowLayoutPanel fwPanel = new FlowLayoutPanel();
private void createFLP()
{
int panelWidth = width * 4 + 50;
int panelHeight = height * 4 + 50;
fwPanel.Size = new Size(panelWidth, panelHeight);
fwPanel.Location = new Point(0, 0);
this.Controls.Add(fwPanel);
}
that solves the nothing happen part. Followed by using this to remove controls
if (fwPanel != null)
{
List<Control> listControls = fwPanel.Controls.Cast<Control>().ToList();
foreach (Control control in listControls)
{
fwPanel.Controls.Remove(control);
control.Dispose();
}
listOfFile.Clear();
}
and everything works like a charm, hope that this answer will be able to help others who are facing the same problem too.

Related

c# how to add Button to array and set text of all buttons

Hi my problem is i want to add buttons to array size: 65
and there is a problem here's my code:
Button[] buttonsa = new Button[65];
for (int d = 0; d <= buttonsa.Length; d++)
{
try
{
buttonsa[d].Text = "Runned!";
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
My all code for creating button to scene:
private void CreateButton(int xh, int yh, int width, int height)
{
Button newButton = new Button();
if (width < 100)
{
newButton.Width = 100;
}
else
{
newButton.Width = width;
}
if (height < 30)
{
newButton.Height = 30;
}
else
{
newButton.Height = height;
}
newButton.Text = "button" + btnIndex;
newButton.ForeColor = Color.FromArgb(35,35,35,0);
newButton.Name = "btn" + btnIndex.ToString();
newButton.AccessibleName = btnIndex.ToString() + "|FlatStyle=" + FlatStyle.System.ToString() + "|Pussy=sussy";
newButton.ContextMenuStrip = btnContext;
newButton.Tag = "button" + btnIndex;
newButton.FlatStyle = FlatStyle.Flat;
newButton.BackColor = Color.White;
newButton.Location = new Point(xh, yh);
newButton.Click += newButton_Click2;
buttonsa[btnIndex] = newButton;
btnIndex++;
int x = rand.Next(10, pic.ClientSize.Width - newButton.Width);
int y = rand.Next(10, pic.ClientSize.Height - newButton.Height);
newButton.Location = new Point(xh, yh);
pic.Controls.Add(newButton);
buttonsInScene.Add(newButton);
this.CenterToScreen();
g.Clear(Color.FromArgb(35, 35, 35, 0));
pic.Refresh();
}
Im working on big project if anyone can help me Thanks alot! <3
I tried a few times to fix it i needed to get all my buttons in form and set text to "Runned!"
You can use the recursive method for getting all buttons on the form. You don't need to add all buttons to the array type, anytime you can use your function and gets all buttons on the form and on the child controls.
But, if you want, you can write easy code inside the function, for adding buttons to an array or to the List.
Example:
private void ButtonList (Control cont)
{
foreach (Control c in cont.Controls)
{
if (c is Button)
{
(c as Button).Text = "Hello";
(c as Button).Visible = true;
// and etc...
/*************** example adding button object to list *************
declare listButton property of type List<Button> on your code
listButton.Add((c as Button));
*******************************************************************/
}
if (c.Controls.Count > 0)
{
ButtonList(c);
}
}
}
And you can use this function calling that
ButtonList (MainForm);

C# remove last dynamically created textbox

Almost totally noob here. Im stucked. I know how to delete all dc textboxes but have no idea how to delete only last created one. Thx
piece of my code:
int B = 1;
public void NovIntRazd()
{
TextBox txt = new TextBox();
this.Controls.Add(txt);
txt.Top = B * 30;
txt.Left = 26;
txt.Height = 20;
txt.Width = 65;
txt.Name = "txtIntRazd" + this.B.ToString();
B++;
}
If you only need to ever remove one textbox, you can just keep the reference to the last one:
int B = 1;
TextBox txt;
public void NovIntRazd()
{
txt = new TextBox();
this.Controls.Add(txt);
txt.Top = B * 30;
txt.Left = 26;
txt.Height = 20;
txt.Width = 65;
txt.Name = "txtIntRazd" + this.B.ToString();
B++;
}
public void RemoveLast()
{
if (txt == null) return;
Controls.Remove(txt);
txt = null;
}
Alternatively, a good practice is to put dynamically created controls in their own container, like a panel. It sounds like your case is a perfect use for a FlowLayoutPanel, which will handle control positioning too. When you remove any control, all the others are automatically moved around to the right places.
Then to remove the last added control, you can just do
if (pnl.Controls.Count > 0) pnl.Controls.RemoveAt(pnl.Controls.Count - 1);
If you really want to keep the controls mixed up in the top-level form, you can easily organize the dynamic textboxes by using a Stack:
Stack<TextBox> textboxes = new Stack<TextBox>();
public void NovIntRazd()
{
var txt = new TextBox()
{
Top = (textboxes.Count + 1) * 30,
Left = 26,
Height = 20,
Width = 65,
Name = "txtIntRazd" + (textboxes.Count + 1).ToString()
};
this.Controls.Add(txt);
textboxes.Push(txt);
}
public void RemoveLast()
{
if (textboxes.Count == 0) return;
Controls.Remove(textboxes.Pop());
}

(C#) Create user control components with for loop and put those components in a list

Of course it's easy to use designer. But, in case for plenty of controls, it hurts to use designer. I'm trying to create a Sudoku table 9 x 9, and need 81 text box with exactly same size. I think, it will be easier to use for loop.
My code so far is like this:
class SudokuTable
{
private List<List<TextBox>> table;
public SudokuTable()
{
initializeComponent();
}
private void initializeComponent()
{
List<List<TextBox>> newTable = new List<List<TextBox>>();
int sz = 30;
int gap = 3;
int x = gap, y = gap;
for (int row = 0; row < 9; row++)
{
x = gap;
List<TextBox> TextBoxes = new List<TextBox>();
for (int col = 0; col < 9; col++)
{
System.Drawing.Size size = new System.Drawing.Size(sz, sz);
System.Drawing.Point location = new System.Drawing.Point(x, y);
System.Drawing.Font font = new System.Drawing.Font("Tahoma", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
TextBox box = new TextBox();
box.Location = location;
box.Size = size;
box.TextAlign = HorizontalAlignment.Center;
box.Font = font;
box.MaxLength = 1;
TextBoxes.Add(box);
x += ((col + 1) % 3 == 0 && (col + 1) < 9) ? sz + gap : sz;
}
newTable.Add(TextBoxes);
y += ((row + 1) % 3 == 0 && (row + 1) < 9) ? sz + gap : sz;
}
table = newTable;
}
public void addSudoku(System.Windows.Forms.Form form)
{
form.SuspendLayout();
foreach (List<TextBox> row in table)
{
foreach (TextBox col in row)
{
form.Controls.Add(col);
}
}
form.PerformLayout();
}
}
And add that table in Main Form with this code:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
SudokuTable st = new SudokuTable();
this.ClientSize = st.getSize();
st.addSudoku(this);
}
}
So far, it works fine. Here's the result:
My problem is, my SudokuTable class can't behave like normal user control. What I mean by normal user control is like when I create a user control via Project --> Add Class --> User Control. My class can't appear in the toolbox.
Is there anyway to create user control components with for loop and make it to behave like normal user control. So I can treat my created control like another control (like add it to Main Form with designer)?
it seems to me you should create new UserControl and incapsulate all this logic in it. Then build solution and UserControl should apear in your toolbox.

Prevent ToolStripMenuItems from jumping to second screen

I have an application that is mostly operated through NotifyIcon's ContextMenuStrip
There are multiple levels of ToolStripMenuItems and the user can go through them.
The problem is, that when the user has two screen, the MenuItems jump to second screen when no space is available. like so:
How can I force them to stay on the same screen? I've tried to search through the web but couldn't find an appropriate answer.
Here is a sample piece of code i'm using to test this senario:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var resources = new ComponentResourceManager(typeof(Form1));
var notifyIcon1 = new NotifyIcon(components);
var contextMenuStrip1 = new ContextMenuStrip(components);
var level1ToolStripMenuItem = new ToolStripMenuItem("level 1 drop down");
var level2ToolStripMenuItem = new ToolStripMenuItem("level 2 drop down");
var level3ToolStripMenuItem = new ToolStripMenuItem("level 3 drop down");
notifyIcon1.ContextMenuStrip = contextMenuStrip1;
notifyIcon1.Icon = ((Icon)(resources.GetObject("notifyIcon1.Icon")));
notifyIcon1.Visible = true;
level2ToolStripMenuItem.DropDownItems.Add(level3ToolStripMenuItem);
level1ToolStripMenuItem.DropDownItems.Add(level2ToolStripMenuItem);
contextMenuStrip1.Items.Add(level1ToolStripMenuItem);
}
}
It is not easy, but you can write code in the DropDownOpening event to look at where the menu is at (its bounds), the current screen, and then set the DropDownDirection of the ToolStripMenuItem:
private void submenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
if (menuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
// Current bounds of the current monitor
Rectangle Bounds = menuItem.GetCurrentParent().Bounds;
Screen CurrentScreen = Screen.FromPoint(Bounds.Location);
// Look how big our children are:
int MaxWidth = 0;
foreach (ToolStripMenuItem subitem in menuItem.DropDownItems)
{
MaxWidth = Math.Max(subitem.Width, MaxWidth);
}
MaxWidth += 10; // Add a little wiggle room
int FarRight = Bounds.Right + MaxWidth;
int CurrentMonitorRight = CurrentScreen.Bounds.Right;
if (FarRight > CurrentMonitorRight)
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
Also, make sure you have the DropDownOpening event hooked up (you would really need to add this to every menu item):
level1ToolStripMenuItem += submenu_DropDownOpening;
I have solved it this way:
For the ContextMenuStrip itself to open on a desired screen, I created a ContextMenuStripEx with the following methods:
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
Rectangle dropDownBounds = new Rectangle(x, y, width, height);
dropDownBounds = ConstrainToBounds(Screen.FromPoint(dropDownBounds.Location).Bounds, dropDownBounds);
base.SetBoundsCore(dropDownBounds.X, dropDownBounds.Y, dropDownBounds.Width, dropDownBounds.Height, specified);
}
internal static Rectangle ConstrainToBounds(Rectangle constrainingBounds, Rectangle bounds)
{
if (!constrainingBounds.Contains(bounds))
{
bounds.Size = new Size(Math.Min(constrainingBounds.Width - 2, bounds.Width), Math.Min(constrainingBounds.Height - 2, bounds.Height));
if (bounds.Right > constrainingBounds.Right)
{
bounds.X = constrainingBounds.Right - bounds.Width;
}
else if (bounds.Left < constrainingBounds.Left)
{
bounds.X = constrainingBounds.Left;
}
if (bounds.Bottom > constrainingBounds.Bottom)
{
bounds.Y = constrainingBounds.Bottom - 1 - bounds.Height;
}
else if (bounds.Top < constrainingBounds.Top)
{
bounds.Y = constrainingBounds.Top;
}
}
return bounds;
}
(ConstrainToBounds method is taken from the base class ToolStripDropDown via Reflector)
for the nested MenuItems to open on the same screen as ContextMenuStrip, I created a ToolStripMenuItemEx (which derives from ToolStripMenuItem). In my case it looks like this:
private ToolStripDropDownDirection? originalToolStripDropDownDirection;
protected override void OnDropDownShow(EventArgs e)
{
base.OnDropDownShow(e);
if (!Screen.FromControl(this.Owner).Equals(Screen.FromPoint(this.DropDownLocation)))
{
if (!originalToolStripDropDownDirection.HasValue)
originalToolStripDropDownDirection = this.DropDownDirection;
this.DropDownDirection = originalToolStripDropDownDirection.Value == ToolStripDropDownDirection.Left ? ToolStripDropDownDirection.Right : ToolStripDropDownDirection.Left;
}
}
The code of #David does not fix if the menu is opened in the left side of second screen. I have improved that code to work on all screen corner.
private void subMenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem mnuItem = sender as ToolStripMenuItem;
if (mnuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
//get position of current menu item
var pos = new Point(mnuItem.GetCurrentParent().Left, mnuItem.GetCurrentParent().Top);
// Current bounds of the current monitor
Rectangle bounds = Screen.GetWorkingArea(pos);
Screen currentScreen = Screen.FromPoint(pos);
// Find the width of sub-menu
int maxWidth = 0;
foreach (var subItem in mnuItem.DropDownItems)
{
if (subItem.GetType() == typeof(ToolStripMenuItem))
{
var mnu = (ToolStripMenuItem) subItem;
maxWidth = Math.Max(mnu.Width, maxWidth);
}
}
maxWidth += 10; // Add a little wiggle room
int farRight = pos.X + mnuMain.Width + maxWidth;
int farLeft = pos.X - maxWidth;
//get left and right distance to compare
int leftGap = farLeft - currentScreen.Bounds.Left;
int rightGap = currentScreen.Bounds.Right - farRight;
if (leftGap >= rightGap)
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
I did not try the solution by tombam. But since the others didn't seem to work, I came up with this simple solution:
private void MenuDropDownOpening(object sender, EventArgs e)
{
var menuItem = sender as ToolStripDropDownButton;
if (menuItem == null || menuItem.HasDropDownItems == false)
return; // not a drop down item
// Current bounds of the current monitor
var upperRightCornerOfMenuInScreenCoordinates = menuItem.GetCurrentParent().PointToScreen(new Point(menuItem.Bounds.Right, menuItem.Bounds.Top));
var currentScreen = Screen.FromPoint(upperRightCornerOfMenuInScreenCoordinates);
// Get width of widest child item (skip separators!)
var maxWidth = menuItem.DropDownItems.OfType<ToolStripMenuItem>().Select(m => m.Width).Max();
var farRight = upperRightCornerOfMenuInScreenCoordinates.X + maxWidth;
var currentMonitorRight = currentScreen.Bounds.Right;
menuItem.DropDownDirection = farRight > currentMonitorRight ? ToolStripDropDownDirection.Left :
ToolStripDropDownDirection.Right;
}
Note that in my world, I was not concerned about multiple levels of cascading menus (as in the OP), so I did not test my solution in that scenario. But this works correctly for a single ToolStripDropDownButton on a ToolStrip.

Trying to create a new page when horizontal pos. of i extends past right margin

I am trying add a page when horizontal or the x position is greater than a counter in order to keep a right side margin. When I run the code I end up in an infinate loop of hundreds of pages all displaying the same first page graphics. Thinking it might have to do with my lack of understanding HasMorePages. I could use some help. Thanks.
public static class PrintWave
{
public static void PrintPreWave()
{
PrintDocument pd = new PrintDocument();
if (WaveTools.MySettings == null)
{
pd.DefaultPageSettings.Landscape = true;
}
else
{
pd.DefaultPageSettings = WaveTools.MySettings;
}
pd.OriginAtMargins = true;
pd.PrintPage += new PrintPageEventHandler(OnPrintPage);
PrintDialog dlg = new PrintDialog();
PrintPreviewDialog printPreviewDlg = new PrintPreviewDialog();
printPreviewDlg.Document = pd;
Form p = (Form)printPreviewDlg;
p.WindowState = FormWindowState.Maximized;
printPreviewDlg.ShowDialog();
}
private static void OnPrintPage(object sender, PrintPageEventArgs e)
{
string MyTag = string.Empty;
MyTag = WaveActions.ActiveId;
Wave MyWave = WaveHolder.FindWave(MyTag);
int MyCount = 0;
int xOffset = e.MarginBounds.Location.X;
int yOffset = e.MarginBounds.Location.Y;
if (MyWave != null)
{
Graphics g = e.Graphics;
g.SetClip(e.PageBounds);
Pen MyPen = new Pen(WaveTools.WaveColor, WaveTools.PenWidth);
float dx = (float)e.PageBounds.Width / MyWave.NumSamples;
float dy = (float)e.PageBounds.Height / 255;
if (MyWave.Normal == false)
{
g.ScaleTransform(dx, dy);
}
for (int i = 0; i < MyWave.NumSamples - 1; i++)
{
g.DrawLine(MyPen, i, MyWave.Data[i], i + 1, MyWave.Data[i + 1]);
MyCount = MyCount + 1;
if (MyCount > e.MarginBounds.Width)
{
e.HasMorePages = true;
MyCount = 0;
return;
}
else
{
e.HasMorePages = false;
return;
}
}
}
}
}
}
for (int i = 0; i < MyWave.NumSamples - 1; i++)
That's the core problem statement, you start at 0 every time PrintPage gets called. You need to resume where you left off on the previous page. Make the i variable a field of your class instead of a local variable. Implement the BeginPrint event to set it to zero.
The else clause inside the loop need to be deleted.

Categories