I want to shown a context menu where the menu items are images that are laid out in a grid. However, when I set LayoutStyle to ToolStripLayoutStyle.Table in the ToolStripDropDown of a menu, it will only give a grid layout of the menu items if a new ToolStripDropDown object is created.
My problem is that I can create and assign a new ToolStripDropDown for a sub-menu, but not for ContextMenuStrip, because it is the ToolStripDropDown.
The following code demonstrates the problem. It will display a context menu that contains colours swatch images and also has two sub-menus with the same images. All three menus have the LayoutStyle property set to ToolStripLayoutStyle.Table, but only one will actually show as a grid.
private void FillDropDown(ToolStripDropDown drop_down)
{
// Set the drop down to a 2 column table layout
drop_down.LayoutStyle = ToolStripLayoutStyle.Table;
TableLayoutSettings table_layout_settings = (TableLayoutSettings)drop_down.LayoutSettings;
table_layout_settings.ColumnCount = 2;
// Fill the menu with some colour swatches
Color[] colours = { Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Purple };
foreach (Color colour in colours) {
ToolStripMenuItem item = new ToolStripMenuItem();
Bitmap swatch = new Bitmap(64, 64);
using (Graphics g = Graphics.FromImage(swatch))
using (SolidBrush b = new SolidBrush(colour)) {
g.FillRectangle(b, 0, 0, 64, 64);
}
item.Image = swatch;
item.DisplayStyle = ToolStripItemDisplayStyle.Image;
item.Margin = new Padding(2, 2, 2, 2);
drop_down.Items.Add(item);
}
}
private void ShowColorMenu(Point screen_location)
{
ContextMenuStrip context_menu = new ContextMenuStrip();
// The root context menu will not layout as a grid
FillDropDown(context_menu);
// This sub-menu will not layout as a grid
ToolStripMenuItem sub_menu = new ToolStripMenuItem("Sub-menu");
FillDropDown(sub_menu.DropDown);
context_menu.Items.Add(sub_menu);
// A sub-menu will layout as a grid if we create a new ToolStripDropDown for it
ToolStripMenuItem grid_sub_menu = new ToolStripMenuItem("Grid Sub-menu");
ToolStripDropDown new_drop_down = new ToolStripDropDown();
FillDropDown(new_drop_down);
grid_sub_menu.DropDown = new_drop_down;
context_menu.Items.Add(grid_sub_menu);
context_menu.Show(screen_location);
}
On my machine the result appears as follows:
I would like to have a grid of images in the root of the context menu. It would also be nice to understand why it is behaving like this. I have looked through the .NET reference source, but that didn't help on this occasion.
The ContextMenuStrip cannot show its ToolStripMenuItems in a Table layout, because of a limitation in Menu presentation/layout and you also cannot cast ContextMenuStrip to ToolStripDropDown (or the other way around; it's a wrapper around ToolStripDropDownMenu, it cannot be transformed into its indirect ancestor: it will retain its specific functionality (e.g., you can see both a TextBox and a ListBox as Control, but it doesn't mean that, now, setting the Text of a ListBox will actually show a text somewhere, just because the Text property belongs to the Control class).
But you can directly use and show a ToolStripDropDown the same way as a ContextMenuStrip. The ToolStripDropDown's LayoutSettings can be cast directly to TableLayoutSettings and a LayoutStyle of type ToolStripLayoutStyle.Table is fully supported.
In the example, a ToolStripDropDown object, containing ToolStripMenuItems arranged in a Table layout, is used as a ContextMenuStrip to select a colored Image to be applied to a PictureBox Control, while the Color name is shown in a Label control.
The dropdown menu is created when the Form is initialized, it's shown clicking the Mouse right Button inside the Form's ClientArea and disposed of when the Form closes:
Note1: here, I'm using a Lambda to subscribe to the ToolStripDropDown.ItemClicked event. It's however preferable, with this type of control, to use a method delegate instead.
Note2: the ToolStripDropDown is disposed of calling contextColorMenu.Dispose();. If the container Form is opened and closed frequently, it may be better to explicitly dispose of the ToolStripMenuItems Images.
using System.Drawing;
using System.Windows.Forms;
public partial class SomeForm : Form
{
private ToolStripDropDown contextColorMenu = null;
public SomeForm()
{
InitializeComponent();
contextColorMenu = new ToolStripDropDown();
contextColorMenu.ItemClicked += (o, a) => {
// Assign the selected Bitmap to a PitureBox.Image
picColor.Image = a.ClickedItem.Image;
// Show the Color description in a Label
lblColor.Text = ((Color)a.ClickedItem.Tag).ToString();
};
FillDropDown(contextColorMenu);
}
private void ShowColorMenu(Point location) => contextColorMenu.Show(location);
private void FillDropDown(ToolStripDropDown dropDown)
{
dropDown.LayoutStyle = ToolStripLayoutStyle.Table;
(dropDown.LayoutSettings as TableLayoutSettings).ColumnCount = 2;
(dropDown.LayoutSettings as TableLayoutSettings).GrowStyle = TableLayoutPanelGrowStyle.AddRows;
Color[] colors = { Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Purple };
foreach (Color color in colors) {
var swatch = new Bitmap(64, 64);
using (var g = Graphics.FromImage(swatch)) {
g.Clear(color);
}
var item = new ToolStripMenuItem() {
DisplayStyle = ToolStripItemDisplayStyle.Image,
Image = swatch,
Tag = color,
Margin = new Padding(2),
Padding = new Padding(2, 1, 2, 1) // Fine tune the Items' Cell border
};
dropDown.Items.Add(item);
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Right) {
ShowColorMenu(MousePosition);
}
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
contextColorMenu?.Dispose();
}
}
Related
I'm trying to make a panel that would host dynamically added controls. There are two caveats:
There are going to be a lot of controls, so the panel should wrap the elements into new rows as it reaches its width limits and scroll vertically.
Controls can change in size, which would change the number of elements
that can fit into a single row.
I've seen a couple proposed solutions to center dynamic controls in a Form and rejected those for following reasons:
TableLayoutPanel - main issue I have with using this are the events when
elements grown and have to shift from 3-2 grid to 2-4, as
TableLayoutPanel does not seem to deal well with those.
AutoSize FlowLayoutPanel that can grow and shrink inside of
TableLayoutControl - my main problem with this solution is that it
only centers one row inside the Form, once it wraps to a new row, the
elements start to align to the right side. I suppose I can dynamically
add new FlowLayoutPanels to new rows of a TableLayoutControl, but then
I have a similar issue as the first scenario where I need to manually
redistribute elements between rows if they grow/shrink in size.
I was wondering if I'm missing some functionality that can help me handle grows/shrink event without creating my own variation of TableLayoutPanel?
Edit:
Below is a draft of functionality:
A - Two elements centered in panel
B - Third element added, all three are centered
C - Forth element added, wrapped to a new row and centered
D - Elements enlarged, now wraps on the second element, centered
Here's an example that reproduces the behaviour you described.
It makes use of a TableLayoutPanel which hosts multiple FlowLayoutPanels.
One important detail is the anchoring of the child FlowLayoutPanels: they need to be anchored to Top-Bottom: this causes the panel to be positioned in the center of a TableLayoutPanel Row.
Note that, in the Form constructor, one of the RowStyles is removed. This is also very important: the TLP (which is quite the eccentric guy), even if you have just one Row (or one Column, same thing), will keep 2 RowStyles. The second style will be applied to the first Row you add; just to the first one, not the others: this can screw up the layout.
Another anomaly, it doesn't provide a method to remove a Row, so I've made one. It's functional but bare-bones and needs to be extended, including further validations.
See the graphic sample about the current functionality. If you need help in implementing something else, leave a comment.
To build this add the following controls to a Form (here, called FLPTest1):
Add one Panel, set Dock.Bottom. Right click and SendToBack(),
Add a TableLayoutPanel (here, called tlp1), set:
AutoScroll = true, AutoSize = true,
AutoSizeMode = GrowAndShrink, Dock.Fill
Keep 1 Column, set to AutoSize and one Row, set to AutoSize
Add a FlowLayoutPanel (here, called flp1), positioned inside the TableLayoutPanel. It's not actually necessary, just for this sample code
Set its Anchor to Top, Bottom <= this is !important, the layout won't work correctly without it: it allows to center the FLP inside the TLP Row,
AutoSize = true, AutoSizeMode = GrowAndShrink
Add a Button (called btnAddControl)
Add a second Button (called btnRemoveControl)
Add a CheckBox (called chkRandom)
Paste the code here inside a Form's code file
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
public partial class TLPTest1 : Form
{
public TLPTest1()
{
InitializeComponent();
tlp1.RowStyles.RemoveAt(1);
}
private void TLPTest1_Load(object sender, EventArgs e)
{
PictureBox pBox = new PictureBox() {
Anchor = AnchorStyles.None,
BackColor = Color.Orange,
MinimumSize = new Size(125, 125),
Size = new Size(125, 125),
};
flp1.Controls.Add(pBox);
tlp1.Controls.Add(flp1);
}
Random rnd = new Random();
Size[] sizes = new Size[] { new Size(75, 75), new Size(100, 100), new Size(125, 125)};
Color[] colors = new Color[] { Color.Red, Color.LightGreen, Color.YellowGreen, Color.SteelBlue };
Control selectedObject = null;
private void btnAddControl_Click(object sender, EventArgs e)
{
Size size = new Size(125, 125);
if (chkRandom.Checked) size = sizes[rnd.Next(sizes.Length)];
var pBox = new PictureBox() {
Anchor = AnchorStyles.None,
BackColor = colors[rnd.Next(colors.Length)],
MinimumSize = size,
Size = size
};
bool drawborder = false;
// Just for testing - use standard delegates instead of Lambdas in real code
pBox.MouseEnter += (s, evt) => { drawborder = true; pBox.Invalidate(); };
pBox.MouseLeave += (s, evt) => { drawborder = false; pBox.Invalidate(); };
pBox.MouseDown += (s, evt) => { selectedObject = pBox; pBox.Invalidate(); };
pBox.Paint += (s, evt) => { if (drawborder) {
ControlPaint.DrawBorder(evt.Graphics, pBox.ClientRectangle,
Color.White, ButtonBorderStyle.Solid);
}
};
var ctl = tlp1.GetControlFromPosition(0, tlp1.RowCount - 1);
int overallWith = ctl.Controls.OfType<Control>().Sum(c => c.Width + c.Margin.Left + c.Margin.Right);
overallWith += (ctl.Margin.Right + ctl.Margin.Left);
if ((overallWith + pBox.Size.Width + pBox.Margin.Left + pBox.Margin.Right) >= tlp1.Width) {
var flp = new FlowLayoutPanel() {
Anchor = AnchorStyles.Top | AnchorStyles.Bottom,
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
};
flp.Controls.Add(pBox);
tlp1.SuspendLayout();
tlp1.RowCount += 1;
tlp1.Controls.Add(flp, 0, tlp1.RowCount - 1);
tlp1.ResumeLayout(true);
}
else {
ctl.Controls.Add(pBox);
}
}
private void btnRemoveControl_Click(object sender, EventArgs e)
{
if (selectedObject is null) return;
Control parent = selectedObject.Parent;
selectedObject.Dispose();
if (parent?.Controls.Count == 0) {
TLPRemoveRow(tlp1, parent);
parent.Dispose();
}
}
private void TLPRemoveRow(TableLayoutPanel tlp, Control control)
{
int ctlPosition = tlp.GetRow(control);
if (ctlPosition < tlp.RowCount - 1) {
for (int i = ctlPosition; i < tlp.RowCount - 1; i++) {
tlp.SetRow(tlp.GetControlFromPosition(0, i + 1), i);
}
}
tlp.RowCount -= 1;
}
}
I have created 4 buttons dynamically and placed them horizontally using c# win forms.Now i want show a custom tooltip(actually its a borderless form) under each of the 4 buttons on mouse hover event.But how do i position my tooltip form under the buttons??
I have tried the code below but it does not work the desired way.
tooltip.Location = new System.Drawing.Point(b.Left, b.Top);
Where 'tooltip' is tooltip form object & 'b' is the dynamic button.Please advise with some code snippet.
private void B_MouseHover(object sender, EventArgs e)
{
var b = sender as Button;
//MessageBox.Show(Cursor.Position.ToString());
if(b!= null)
{
if (tooltip == null)
{
tooltip = new frmSecQStatToolTipDlg();
}
tooltip.Location = new System.Drawing.Point(b.Left, b.Bottom);
tooltip.data(b.Tag.ToString());
tooltip.Show();
}
}
The way you named it is a bit misleading. As I understand, what you call a tooltip is just a Form. You need to consider 2 things
(1) Form.StartPosition must be set to FormStartPosition.Manual
(2) Form.Location must be in screen coordinates. Note that the Button.Location you are trying to use is in button's parent client coordinates. Control.PointToScreen has to be used for conversion.
In your case, it should be something like this
tooltip.StartPosition = FormStartPosition.Manual;
var topLeft = b.PointToScreen(new Point(0, 0));
tooltip.Location = new Point(topLeft.X, topLeft.Y + b.Height);
When you show the tooltip you can control its location, check show method overloads: https://msdn.microsoft.com/en-us/library/system.windows.forms.tooltip.show.aspx
I am Showing a ContextMenu whenever the user right clicks on a specific location in a DataGridView.
I want the items of that ContextMenu to have a back color and fore color depending on their content.
How can I do this since ContextMenu has no back color or Fore color property?
I tried looking up ContextMenuStrip but this has to be connected to a ToolStripButton which I do not have and do not want.
In order to change the back color of a MenuItem you need to specify a draw item handler and set owner-draw to true for each item. Also for the color to actually take some space you need to implement a MeasureMenuItem handler.
So for example
color.MenuItems.Add(new MenuItem("#123456", menuHandler));
color.MenuItems.Add(new MenuItem("Green", menuHandler));
color.MenuItems.Add(new MenuItem("Red", menuHandler));
foreach (MenuItem item in color.MenuItems)
{
item.OwnerDraw = true;
item.DrawItem += item_DrawItem;
item.MeasureItem += MeasureMenuItem;
}
The above codes hooks up the items and their handlers.
void item_DrawItem(object sender, DrawItemEventArgs e)
{
MenuItem cmb = sender as MenuItem;
string color = SystemColors.Window.ToString();
if (e.Index > -1)
{
color = cmb.Text;
}
if (checkHtmlColor(color))
{
e.DrawBackground();
e.Graphics.FillRectangle(new SolidBrush(ColorTranslator.FromHtml(color)), e.Bounds);
e.Graphics.DrawString(color, new Font("Lucida Sans", 10), new SolidBrush(ColorTranslator.FromHtml(color)), e.Bounds);
}
}
The above code takes the MenuItem contents, converts it to a color, creates a rectangle for that color and draws it.
void MeasureMenuItem(object sender, MeasureItemEventArgs e)
{
MenuItem m = (MenuItem)sender;
Font font = new Font(Font.FontFamily, Font.Size, Font.Style);
SizeF sze = e.Graphics.MeasureString(m.Text, font);
e.ItemHeight = (int)sze.Height;
e.ItemWidth = (int)sze.Width;
}
And lastly the above few lines simply measure the area the MenuItem should take before drawing (basically measures the space of it's string content) so the draw_item handler knows how much space to take up
I allow myself to dig up the post because I've had the same problem (add background color to MenuItem in ContextMenu) and found this post.
But the answer seemed very complicated. So I continued to search and find a simplet solution : Use ContextMenuStrip and ToolStripMenuItem
Here is an example for users who has the same problem :
ContextMenuStrip cMenu=new ContextMenuStrip();
ToolStripMenuItem mi;
// Item 1, null in constructor to say : no picture on the label
mi=new ToolStripMenuItem("item 1",null , (s,a)=> actionOnClicItem1());
mi.BackColor = Color.Red;
cMenu.Items.add(mi);
// Separator
cMenu.Items.Add(new ToolStripSeparator());
// Item 2
mi=new ToolStripMenuItem("item 2",null , (s,a)=> actionOnClicItem2());
mi.BackColor = Color.Blue;
cMenu.Items.add(mi);
// show the context menu near by the mouse pointer
cMenu.Show(myDataGridView,new Point(e.X,e.Y));
myToolStripMenuItem.GetCurrentParent().BackColor = Color.Red
When I add item(s) to the ListView in any other than LargeIcon view mode, the ListView stops showing the images from LargeImageList when it is switched back to LargeIcon. This situation lasts until new item is added to the ListView in the LargeIcon mode.
So the following sequence illustrates the problem:
create ListView, add column, set View to Details
create ImageList, set ImageSize, assign it to ListView.LargeImageList
create new ListViewItem, set its ImageKey
create new image, add it to the ImageList with given key
add the ListViewItem to the ListView
switch the ListView mode to LargeIcon
no images are shown
repeat steps #3 - #6, now in the LargeIcon mode
all images are shown as expected
What is the point I am still missing?
I have tried following:
Invalidate the ListView
Re-assign the LargeImageList before/after adding the item (even through null)
The test code for those who like it more than words:
public partial class Form1 : Form
{
int counter = 0;
ImageList iList = new ImageList();
private string GetNewKey()
{
return counter++.ToString();
}
private Image GetNewImage(Size size)
{
var bmp = new Bitmap(size.Width, size.Height);
using (var gra = Graphics.FromImage(bmp))
{
var rnd = new Random();
var lines = rnd.Next(1000);
for (int l = 0; l < lines; ++l)
{
var pen = new Pen(Color.FromArgb(rnd.Next(256), rnd.Next(256), rnd.Next(256)));
var p1 = new Point(rnd.Next(size.Width), rnd.Next(size.Height));
var p2 = new Point(rnd.Next(size.Width), rnd.Next(size.Height));
gra.DrawLine(pen, p1, p2);
}
}
return bmp;
}
public Form1()
{
InitializeComponent();
iList.ImageSize = new Size(100, 100);
listView.LargeImageList = iList;
listView.Columns.Add("name");
}
private void buttonAdd_Click(object sender, EventArgs e)
{
var key = GetNewKey();
var lvi = new ListViewItem()
{
Name = key,
Text = "blabla",
ImageKey = key,
};
iList.Images.Add(key, GetNewImage(new Size(100, 100)));
listView.Items.Add(lvi);
}
private void buttonClear_Click(object sender, EventArgs e)
{
listView.Items.Clear();
}
private void buttonLarge_Click(object sender, EventArgs e)
{
listView.View = View.LargeIcon;
}
private void buttonDetails_Click(object sender, EventArgs e)
{
listView.View = View.Details;
}
}
EDIT:
For anyone who would suffer the same problem. After some experiments, there is at least stupid poor man's workaround:
Modify the ImageList, the ListView somehow detects its change and reloads the images for LargeIcon mode. The questions are how it detects the change and why does it ignore the ImageList after mode change...
private void FixIt()
{
// Trigger a reload of the ListView.LargeImageList
if (listView.View == View.LargeIcon)
{
var key = "Dummy image to be deleted right after its insertion...";
iList.Images.Add(key, new Bitmap(1, 1));
iList.Images.RemoveByKey(key);
}
}
EDIT #2:
I have also discovered some other funny features the ListView and associated components have. You might want to check them in answers of question 4097912 and question 23059678
To solve your problem
You could avoid this by useing ImageIndex instead of ImageKey to connect your ListView with the ImageList. So in your buttonAdd_Click event use:
var lvi = new ListViewItem()
{
Name = key,
Text = "blabla",
//ImageKey = key,
//Use ImageIndex and don't set both
ImageIndex= Convert.ToInt32(key) //you could just use count++
};
The reason behind this problem:
The reason behind this is not clear to me, but I assume this may be a bug that when changing from Details to LargeIcon, it only checks ImageIndex in default and if you set ImageKey the ImageIndex will be set to -1. Or may be this is by design, I don't know (see ImageKey section below), since you don't have a SmallImageList, so when changing to LargeIcon view, ImageIndex is null or -1 and the ImageKey is ignored.
About ListViewItem.ImageIndex Property
The ImageKey and ImageIndex properties are mutually exclusive, meaning if one is set, the other is ignored. Furthermore, if you set the ImageKey property, the ImageIndex property is automatically set to -1. Alternatively, if you set the ImageIndex property, the ImageKey is automatically set to an empty string ("").
About ListViewItem.ImageKey Property
If you are using multiple image lists, for small and large icon view, with a ListView control, you should place small and large versions of the image at the same index location in their respective image lists. When switching between views, the index location of the image in one list is used to locate the image in the other list, regardless of the key value specified.
And this can somehow be verified:
using your existing code (use ImageKey)
set ImageIndex for any Item in your listView within the buttonLarge_Click event handler will show you that item's image.
set ImageKey for any Item within the buttonLarge_Click event handler will not show that itme's image.
e.g.:
private void buttonLarge_Click(object sender, EventArgs e)
{
listView.View = View.LargeIcon;
//Set ImageIndex of Item 0 you could see its Icon.
listView.Items[0].ImageIndex= 0 ;
//set ImageKey will change nothing
//listView.Items[0].ImageKey= "0" ;
}
I have a WrapPanel that contains multiple Canvas of the same size. Each Canvas has some UIElements (i.e. TextBox, TextBlock, Buttons etc) as children. The creation of each Canvas (including its UIElement children) and the number of Canvas to be created are all done in run-time code behind (no XAML).
Initially I did the following, which works:
// declare as class properties, so all function can access them
WrapPanel wp = new WrapPanel();
Canvas[] cv = new Canvas[500];
TextBox[] tb = new TextBox[500];
// A function (e.g. a Button_Click event) that generates multiple Canvas in a WrapPanel
for (int i = 0; i<myInt; i++)
{
cv[i] = new Canvas();
tb[i] = new TextBox();
cv[i].Children.Add(tb[i]);
wp.Children.Add(cv[i]);
}
The above code is straight forwards works OK - Until I implement add, minus and destroy buttons where I could
1. Add an additional `Canvas` on a click event
2. Remove the last `Canvas` on a click event
3. Destroy a specific `Canvas` in the `WrapPanel` on a click event (may ba a little cross icon in each `Canvas`)
If I process some combination of the above 3 actions, I could easily create UIElements of the same index or create Canvas that goes out of the range of what it had been declared initially.
I looked into List however, each Canvas have different properties (each also has UIElement Children with different properties) and I can't figure out how List would solve it. A way for me to go around that is to declare a super large Array size for Canvas (e.g. Canvas[] cv = new Canvas[99999] but I though that's not very efficient.
Also, if I use List, how could I change properties of a specific UIElement after the they are generated? E.g. If i add 10 Canvas and add to List, and after they are all generated, I need to select the 5th Canvas and change a TextBox.Text, how do I access it like I did in an Array (i.e. tb[5].Text = "Hello")?
Can anyone show me some approaches to this problem?
Just a direct translation on how to do this with a list instead below. Given your code I don't know why you want to keep track of the canvas and textbox'es in a list - you can just access the children collection of the WrapPanel directly instead - let's assume you do need these separate collections for now.
WrapPanel wp = new WrapPanel();
List<Canvas> cvList = new List<Canvas>();
List<TextBox> tbList = new List<TextBox>();
public void Init()
{
int myInt = 500;
// in a function (e.g. a Button_Click event) to generate the multiple Canvas in a WrapPanel
for (int i = 0; i < myInt; i++)
{
Canvas cv = new Canvas();
TextBox tb = new TextBox();
cv.Children.Add(tb);
wp.Children.Add(cv);
cvList.Add(cv);
tbList.Add(tb);
}
}
public void AddCanvas()
{
Canvas cv = new Canvas();
TextBox tb = new TextBox();
cv.Children.Add(tb);
wp.Children.Add(cv);
cvList.Add(cv);
tbList.Add(tb);
}
public void RemoveCanvas()
{
wp.Children.RemoveAt(wp.Children.Count-1);
cvList.RemoveAt(cvList.Count - 1);
tbList.RemoveAt(cvList.Count - 1);
}
Edit for added comment:
E.g. If i add 10 Canvas, and after
they are all generated, I need to
select the 5th Canvas and change a
TextBox.Text, how do I access it like
I did in an Array (i.e. tb[5].Text =
"Hello")?
You can just access the children directly. You know you only added Canvas elements to your WrapPanel. So you could do (wp is the WrapPanel again):
TextBox textbox = (wp.Children[5] as Canvas).Children[0] as TextBox;
textbox.Text = "Hello";
Just operate directly on the WrapPanel's Children collection.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AddCanvasToWrapPanel(this.TestWrapPanel);
RemoveLastCanvasFromWrapPanel(this.TestWrapPanel);
AddCanvasToWrapPanel(this.TestWrapPanel);
DestroyCanvasAtWrapPanelIndex(this.TestWrapPanel, 0);
}
private void AddCanvasToWrapPanel(WrapPanel wp)
{
TextBox t = new TextBox();
Canvas c = new Canvas();
c.Children.Add(t);
wp.Children.Add(c);
}
private void RemoveLastCanvasFromWrapPanel(WrapPanel wp)
{
wp.Children.RemoveAt(wp.Children.Count - 1);
}
private void DestroyCanvasAtWrapPanelIndex(WrapPanel wp, int index)
{
wp.Children.RemoveAt(index);
}
}
}