I have a row of panels on my WindowsForm which have two buttons on each, Move Up and Move down. I want it so that when the user clicks down it switches the current panel with the panel beneath it essentially taking its place. (swapping them over). I have 10 instances of the same panel so something simple like the below isnt working because all the panels are called DataPanel. Any help is appreciated.
private void MoveDownEnabled_Click(object sender, EventArgs e)
{
Point temp = DataPanel.location;
DataPanel.Location = Panel2.Location;
Panel2.Location = temp;
}
I would recommend storing the Panel in a collection, for a start. This will make the code for accessing the next panel, very clean.
ArrayList panels = new ArrayList();
panels.add(panel);
panels.add(panel2);
panels.add(panel3);
ANd also keep a value, index, to show where in the list you're up to.
private int index = 0; // Set it to a default value.
Then ammend your button click code:
private void MoveDownEnabled_Click(object sender, EventArgs e)
{
Panel Current = panels.get(index);
if(index < panels.Count)
{
// Grab the next item.
Panel Next = panels.get(++ index);
// And this part is up to you!
}
}
You seem to have the right idea with how you're swapping them around, so I think now I've set this up for you, you can work the rest out for yourself.
Don't use your variable name to differentiate controls at runtime, use their properties or their instances. In this case, you can use the Panel's Name property to differentiate, or its Tag property which can hold an object type. Also, I recommend storing the Panels in a collection such as a List so that they can be iterated and accessed easily.
For example (as a general idea)
//won't get nested panels but sounds like you don't need that
var listOfPanels = this.Controls.OfType<Panel>().ToList();
//access the panel you want by name
Panel pnl = listOfPanels.Where(x => x.Name == "panelName").FirstOrDefault();
In your case, your Panel events will be send the panel as the sender in your event handler.
private void MoveDownEnabled_Click(object sender, EventArgs e)
{
Panel selectedPanel = sender as Panel;
if(selectedPanel != null)
{
Point temp = DataPanel.location;
DataPanel.Location = selectedPanel.Location;
selectedPanel.Location = temp;
}
}
I think a simple swap method should do:
public void SwapPanels(System.Windows.Forms.Panel pnl1, System.Windows.Forms.Panel pnl2)
{
var localtion1 = pnl1.Location;
var location2 = pnl2.Location;
pnl2.Location = localtion1;
pnl1.Location = location2;
}
if you want to swap any two panels, just call this method to swap them. if you have many panels, just store them in a list or so and swap them freely.
Related
I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.
I have a User Control that contains a combobox/drop down list. This user control is used multiple times and added dynamically to a panel. When I change the value of the combobox on one user control, it changes the rest? Does anyone know how to sort this?
So to clarify. When I change the value in the combobox of the top usercontrol (7002), it will change the second user controls combobox value to whatever I selected.
Thanks!
Code for adding the controls;
foreach (Common.UserDTO UDTO in BLL.User.GetAllUsers())
{
Admin_UserControls.UserBar UB = new Admin_UserControls.UserBar(UDTO);
UB.Location = new Point(0, int.Equals(pnlUserBlock.Controls.Count, 0) ? 0 : pnlUserBlock.Controls[pnlUserBlock.Controls.Count - 1].Bottom);
pnlUserBlock.Controls.Add(UB);
}
constructor/load events:
private Common.UserDTO UDTO;
public UserBar(Common.UserDTO UDTO)
{
InitializeComponent();
/* Store the passed in UserDTO */
this.UDTO = UDTO;
}
private void UserBar_Load(object sender, EventArgs e)
{
/* Setup the Drop down list */
cbRanks.DataSource = Common.Helper.GetRanksDT();
cbRanks.DisplayMember = "Rank";
cbRanks.ValueMember = "ID";
/* Setup the users */
lblUsername.Text = UDTO.Username;
cbRanks.SelectedValue = UDTO.RankID;
}
Put the above comment into an answer should others have the same issue in future.
Each instance of the UserControl that is created is bound to the same DataSet giving you this result.
Caused by the line:
cbRanks.DataSource = Common.Helper.GetRanksDT();
To resolve this simply declare a new instance each time the UserControl is created, see this post that discusses a few methods.
I tried to find the answer by googling but no luck.
I designed form with panel containing textboxes so I can iterate through its controls
and save every textbox in an array (so I can iterate the array when I want to),
the thing is I couldn't find which property the panel knows how to arrange the order to iterate the controls inside it, which is the first, second,etc..
I thought maybe it's by tag, but when I changed them to my likings it didn't change anything.
so I wonder - how can you tell the panel iteration to go through the controls as you prefer?
which property do you need change?
private void CreateTxtArr()
{
txts = new TextBox[8];
for (int i = 0; i < pnlTxt.Controls.Count; i++)
txts[i] = (TextBox)pnlTxt.Controls[i];
}
You can use is operator to check if the child control of panel is TextBox
private void CreateTxtArr()
{
txts = new TextBox[8];
for (int i = 0; i < pnlTxt.Controls.Count; i++)
if( pnlTxt.Controls[i] is TextBox)
txts[i] = (TextBox)pnlTxt.Controls[i];
}
If you can use .Net 3.5 or higher, you can do it pretty easly with Linq:
private void CreateTxtArray()
{
// txts is an Array of TextBox
var txts = (from Control ctrl in pnlTxt.Controls
where ctrl is TextBox
select ctrl as TextBox).ToArray();
}
Is a simpler way to iterate, in all cases
EDIT: this is an old answer I had forgotten about, below there is an even simpler way to do that
private void CreateTxtArray()
{
// txts is an Array of TextBox
var txts = pnlTxt.Controls.OfType<TextBox>().ToArray();
}
I have a TreeView control for which each node in it I want to share a ContextMenuStrip which has two ToolStripMenuItems ie:
this.BuildTree = new MyApp.MainForm.TreeView();
this.ItemMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components);
this.DeleteMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ShowLogMenuItem = new System.Windows.Forms.ToolStripMenuItem();
...
this.ItemMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.DeleteMenuItem,
this.ShowLogMenuItem});
So I show and hide these to items according to certain criteria on a right click in a MouseUp event. When both are hidden I hide the ContextMenuStrip itself. Problem is when I hide the ContextMenuStrip it seems the next time I want to show one of the menu items I have to click twice on the node. The strange thing is on the first click to reshow one or both of the the items I have the following code:
ItemMenuStrip.Visible = true;
ShowLogMenuItem.Visible = true;
The two lines above don't seem to do anything ie both remain false in the debugger view after stepping over each line.
I don't think I've got any events on these values being set at least I don't have any events attached.
What am I doing wrong?
I suggest you to set:
this.BuildTree.ContextMenuStrip = this.ItemMenuStrip;
to make the menu automatically open on tree right-click.
Then subscribe ItemMenuStrip.Opening event to change the visibility of items and the contextmenu itself:
void ItemMenuStrip_Opening(object sender, CancelEventArgs e)
{
if (something)
{
e.Cancel = true; // don't show the menu
}
else
{
// show/hide the items...
}
}
If you need to know the current position of the clicked point (e.g. to check if a tree node is clicked), you can use Control.MousePosition property. Note that MousePosition is a point in screen coordinates, so you need to call treeView1.PointToClient(position) to get the tree coordinates e.g. :
private void ItemMenuStrip_Opening(object sender, CancelEventArgs e)
{
var pointClicked = this.BuildTree.PointToClient(Control.MousePosition);
var nodeClicked = this.BuildTree.GetNodeAt(pointClicked);
if (nodeClicked == null)
{
// no tree-node is clicked --> don't show the context menu
e.Cancel = true;
}
else
{
// nodeClicked variable is the clicked node;
// show/hide the context menu items accordingly
}
}
So figured out what was going wrong I was setting Visible on this.ItemMenuStrip rather than the this.BuildTree.ContextMenuStrip.
This seems rather strange to me as I would have thought BuildTree.ContextMenuStrip was just a direct reference to the ItemMenuStrip but apparently not.
I'm trying to make a list of controls. For this, I used a flow layout panel and a custom item. After reading a XML file I populate the flow layout panel with my items. For a small number of items everything seams to be OK but for a number like 371 of items in the flow layout panel something get's wrong. At the bottom of the flow layout panel 95 items are missing, and seams to be overlapped. The space located for this items, i think, is still created. I attach a screen with the effect produced. The controls are created in an array and then I iterate that array to add the controls to the flow layout panel.
http://img510.imageshack.us/img510/3201/screen2011916213527199.jpg
Thank you.
LE:
public delegate void AddHistoryItemDelegate(Control itm);
public void AddHistoryItem(Control itm)
{
if (InvokeRequired)
{
Invoke(new AddHistoryItemDelegate(AddHistoryItem), new object[] { itm });
}
else
{
flowLayoutPanel1.Controls.Add(itm);
}
}
foreach (Control c in histroryItems)
{
controls++;
backgroundWorkerLoadHistory.ReportProgress(controls);
//flowLayoutPanel1.Controls.Add(c);
AddHistoryItem(c);
}
The delegate is there because all this is taking place in a separate thread. histroryItems is a List of controls.
LE: If it's counts, i observed that, if I remove an item from the list, after the list loads, it get's arranged. Trying a little hack to see if adding and removing a control at the end of the thread, does not have any effect.
You could try this:
this.flowLayoutPanel1.SuspendLayout();
before adding controls and:
this.flowLayoutPanel1.ResumeLayout();
after adding controls. Maybe the following should be performed consecutively:
this.flowLayoutPanel1.PerformLayout();
and/or:
this.flowLayoutPanel1.Refresh();
I found that I had to do this:
private void flpChoices_Scroll(object sender, ScrollEventArgs e)
{
Control c=flpChoices.GetChildAtPoint(new Point(10, 10), GetChildAtPointSkip.None);
if (c == null) flpChoices.PerformLayout();
}
Where flpChoices is my FlowLayout Panel. Now I don't think the scroll event is triggered on mouse wheel movement, so I don't know what to do about that.
EDIT: There is a hidden(not in the property window) even for scroll wheel:
void flpChoices_MouseWheel(object sender, MouseEventArgs e)
{
Control c=flpChoices.GetChildAtPoint(new Point(10, 10), GetChildAtPointSkip.None);
if (c == null) flpChoices.PerformLayout();
}