Common class for WPF Containers (Canvas, Grid, etc) - c#

I am creating a method that is invoked by a button. The method then adds a canvas to the button's parent container control. So, e.g., the button is on a grid. Then the method creates a canvas that is shown just below the button. But I have 2 issues:
How can I get a reference to the button's parent container?
Is there a class for container controls? I don't care if the button is in a grid, a canvas, a Stackpanel, etc. So I am looking for an interface that all types of contianers implement or a class that they inherit.
The second aspect is more important as I could pass a reference to the container manually.
EDIT:
It should look like this (minus the colors, those are only to show the different elements.
The red canvas is supposed to pop up to handle a confirmation. Maybe even with a nice animation. My idea was to create a class that can be invoked similar to this:
MyPopup popup = new MyPopup("Are you sure?", "Yes", "No", delegateFirstButton, delegateSecondButton);
popup.Show();
My code so far is not yet a class but only a method. The text parts are hard coded for the moment. The marked line needs to be more flexible and is the reason for my question.
public void ShowPopup(Control senderControl)
{
//I need to have a parameter that accepts all containers instead of this line:
this.myGrid.Children.Add(popup);
Border border = new Border();
popup.Children.Add(border);
border.Margin = new Thickness() { Top = 10 };
border.Child= text;
text.Text = "Are you sure?";
text.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
popup.SizeChanged += delegate { border.Width = popup.ActualWidth; };
popup.Children.Add(btn1);
btn1.Content = "Yes";
btn1.Height = 22;
btn1.Padding = new Thickness(10, 0, 10, 0);
btn1.Margin = new Thickness() { Left = 15, Top = 35 };
popup.Children.Add(btn2);
btn2.Content = "No";
btn2.Height = 22;
btn2.Padding = new Thickness(10, 0, 10, 0);
btn1.SizeChanged += delegate { btn2.Margin = new Thickness() { Left = 30 + btn1.ActualWidth, Top = 35 }; };
popup.Height = 70;
btn2.SizeChanged += delegate
{
popup.Width = 45 + btn1.ActualWidth + btn2.ActualWidth;
updatePositions(senderControl);
};
popup.Background = Brushes.Red;
popup.VerticalAlignment = System.Windows.VerticalAlignment.Top;
popup.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
}
public void updatePositions(Control senderControl)
{
Point location = senderControl.TranslatePoint(new Point(0, 0), this.myGrid);
popup.Margin = new Thickness()
{
Left = location.X + (senderControl.ActualWidth / 2) - (popup.Width / 2),
Top = location.Y + senderControl.ActualHeight + 15
};
}

Sounds like you're choosing the hard way to do that.
If you need a popup, then use a Popup.
Otherwise, if for whatever reasons you don't want to use that, You'd better place a grid somewhere near the root of your XAML, and ALWAYS use that as a container:
<Window>
<Grid x:Name="MainUI"/>
<Grid x:Name="PopupContainer"/>
</Window>
Otherwise, you'll almost invariably encounter Z-Index problems (if you follow your current approach).
Also, it's a really bad idea to create all that UI stuff in code. Either encapsulate your Yes/No dialog in a UserControl or create a proper Template for that.
As I said before, avoid at all costs creating/manipulating UI elements in code, as it creates a lot of maintainability issues.

Related

Location of panel not correct when using different anchor style

this is for Winforms / C# btw.
So I am making my own ComboBox, just playing around.
The item List is basically a Panel, which is located under or above the ComboBox control, depending on its height and location.
This works as intended, as long as I use AnchorStyle Top / Left for my usercontrol.
As soon as I switch to Top / Right Style, the location of my listpanel breaks. Okay, makes sense, the location point of my usercontrol changes with the different anchorstyle, so how about I adjust my panel to a new location using my usercontrol.LocationChanged event? Doesen't work. Maybe because of the order of the events? Or does the origin of locations change depending on the given AnchorStyles?
Anyway, I am a bit lost. Unfortunately I couldn't find a similar case here, therefore this question.
Here's my DropDownPanel (effectively my "list"):
private Panel DropDownPanel()
{
Panel DropDownPanel = new Panel() {
Width = this.Width,
Height = 200,
BackColor = _skin.TextBoxBackColor,
BorderStyle = BorderStyle.FixedSingle,
ForeColor = _skin.TextBoxForeColor,
Visible = false,
AutoScroll = false
};
DropDownPanel.HorizontalScroll.Enabled = false;
DropDownPanel.HorizontalScroll.Visible = false;
DropDownPanel.HorizontalScroll.Maximum = 0;
DropDownPanel.AutoScroll = true;
DropDownPanel.Leave += DropDownPanel_Leave;
return DropDownPanel;
}
Here's my userControl Load Event:
private void GbComboBox_Load(object sender, EventArgs e)
{
_dropDownPanel = DropDownPanel();
this.Parent.Parent.Controls.Add(_dropDownPanel);
RelocateDropDownPanel();
_dropDownPanel.BringToFront();
ApplyItems(_items);
}
And my calculation for the location:
private void RelocateDropDownPanel()
{
if (_dropDownPanel != null)
{
int initLocY = this.Parent.Location.Y + this.Location.Y + this.Height + _dropDownPanel.Height;
int fullHeight = this.FindForm().Height;
Point p = new Point();
if (initLocY < fullHeight)
{
p = new Point(this.Parent.Location.X + this.Location.X, initLocY - _dropDownPanel.Height);
}
else
{
p = new Point(this.Parent.Location.X + this.Location.X, initLocY - _dropDownPanel.Height - this.Height);
}
_dropDownPanel.Location = p;
}
}
So I figured out a "solution".
I simply inherited the Anchor of my Panel from the Usercontrol it was associated to.
But I wanna mention, that still, this whole thing is kind of a crappy way of dealing with custom combobox desings. As #Jimi stated in his first comment, the correct way of doing this would be to ownerdraw the combobox. I haven't done this because of specific reasons, which didn't let me do this.
A little example on how this could be made:
Change ComboBox Border Color in Windows Forms

Xamarin.Forms CollectionView View Recycling Issues

So I am using a CollectionView to display a list of entities that, when pressed, toggle between a opened and closed state. I did this by animating the HeightRequest parameter of the parent container of the view that was pressed, then adding whatever views I wanted to show in the view's expanded state. Here is the code snippet for that:
var animate = new Animation(d => this.HeightRequest = d,
this.Bounds.Height, this.Bounds.Height + 300, Easing.CubicOut);
animate.Commit(this, "a", length: 500);
this.layout.Children.Add(this.candidateList);
this.layout.Children.Add(this.openButton);
This works fine, however, if I scroll down the list, I see that there are views that are also expanded even though I did not touch them previously, more so every full page of scrolling later. Some even include the views that I've added to the expanded state, showing incorrect data. I have assumed that this is due to the recycling mechanics of the CollectionView work in order to save on rendering costs, but there must be some way to fix this. Here is the code for relevant views:
var officesList = new CollectionView
{
ItemTemplate = new DataTemplate(typeof(OfficeListView)),
HorizontalOptions = LayoutOptions.FillAndExpand,
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
SnapPointsType = SnapPointsType.None,
ItemSpacing = 10,
},
Margin = new Thickness(20, 5),
ItemSizingStrategy = ItemSizingStrategy.MeasureAllItems,
Footer = " ",
FooterTemplate = new DataTemplate(() =>
{
return new StackLayout
{
Margin = new Thickness(20, 10),
Children = {
new LocorumLabels.MediumLabel
{
Text = "x Results | No filters applied" //TODO: Bind these to footer
}
}
};
})
};
The next snippet is the CollectionsView within an expanded "Office":
this.candidateList = new CollectionView
{
ItemTemplate = new DataTemplate(typeof(CandidateDetailView)),
HorizontalOptions = LayoutOptions.FillAndExpand,
ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical)
{
SnapPointsType = SnapPointsType.None,
//ItemSpacing = 10,
},
Margin = new Thickness(10, 5),
ItemSizingStrategy = ItemSizingStrategy.MeasureAllItems,
Footer = " ",
HeightRequest = 300
};
And here is a video showing what is going on:
https://youtu.be/Ltg2o8BwfwY
Hopefully someone can let me know of a solution. Many thanks.
You are right on the cause.
As CollectionView uses DataTemplate you need to set your views and your data in such a way that when recycled the view appears as it should.
The non-obvious part to keep the animation working is to call the animation as of now but when it completes to set the value in data that would alter the view from the state before the animation to the state after the animation.

Drop Down Menu in c# (position and use)

For my project in school, I have to create a window and program that has a menu, and for the menu, I need to have a drop down menu.
The drop down menu works and all, however, I cannot seem to get it to go into the lower part of my window.
Here is my code:
var MenuWindow = new Form();
MenuWindow.Height = 300;
MenuWindow.Width = 300;
MenuWindow.Text = "Menu";
ToolBarButton Numbers = new ToolBarButton("Number of players");
ToolBar DropDownMenu = new ToolBar();
MenuItem menuItem1 = new MenuItem("1");
MenuItem menuItem2 = new MenuItem("2");
ContextMenu ContentsOfMenu = new ContextMenu(new MenuItem[] { menuItem1, menuItem2 });
DropDownMenu.Buttons.Add(Numbers);
Numbers.Style = ToolBarButtonStyle.DropDownButton;
Numbers.DropDownMenu = ContentsOfMenu;
DropDownMenu.Left = 95;
DropDownMenu.Top = 215;
DropDownMenu.Width = 100;
DropDownMenu.Height = 10;
MenuWindow.Controls.Add(DropDownMenu);
MenuWindow.Show();
As you can see I have tried to add the Height, Width, Left and Top coordinates. However, this has done no difference to where the position of this drop down menu.
I have used a ToolBar and a ToolStrip and the result is the same; I cannot move the position of the button/drop down menu.
Also if you have any suggestions on how to improve my code, please do as I am new to this part of c#.
The first thing to consider is that the name of your variable "DropDownMenu" is misleading. That variable is a ToolBar, not a drop down menu. When you're actually selecting from the drop down on your form, the control you are using is "Numbers". "Numbers" is really what should be called "DropDownMenu".
Given this, I suspect the reason you can't change the Top and Left properties of your toolbar is because it is in fact a toolbar. Toolbars just go at the extremes of their containers. It never makes sense to put them in the middle. It's odd that they even have this property, but I'm guessing that it's because they inherit from "Control". The rendering engine must simply ignore these properties. If you really must have this, take a look at Hans Passant's comment under your question.
I don't work too much with Windows Forms Controls, but I do recall that typically you're more likely to want "ComboBox" for this. And put the combo box straight on your form, bypassing any toolbars. Like this:
var label =
new Label() {
Text = "Players",
Top = 20,
Left = 20,
Width = 75
};
var combo =
new ComboBox() {
Left = label.Left + label.Width,
Top = label.Top,
Width = 150,
Height = 10
};
combo.Items.Add("1");
combo.Items.Add("2");
var MenuWindow =
new Form() {
Height = 100,
Width = 300,
Text = "Menu"
};
MenuWindow.Controls.Add(combo);
MenuWindow.Controls.Add(label);
MenuWindow.Show();
Also, since you're new, note the different style. It's not particularly better, but it's worth knowing about. Setting properties in this manner helps me block things together better. Also, it's generally okay to use the "var" keyword when it's painfully obvious what kind of variable you have. So, for instance, it is very clear that combo is a ComboBox. Consequently I just use "var". When it's even a little unclear, then write out the actual type.

Fast way to add Controls to a TableLayoutPanel

I have to add a lot of Control objects to a TableLayoutPanel dynamically, what takes a significant amount of time. I also need to be able to access the controls via row- and column-index of the TableLayoutPanel and vice versa.
TableLayoutPanel.Controls has (as far as I know) 3 ways to add Control objects:
.Add(Control) - inherited, position is -1,-1 with .GetCellPosition(Control)
.Add(Control, column, row) - position and indexes are correct, but maybe a bit slow?
.AddRange (Control[]) - inherited, faster, shown position is correct (every cell is filled, if necessary columnspans are set afterwards), but position is -1,-1 with .GetCellPosition(Control)
Is there a way to combine the advantages of .Add(Control, column, row and .AddRange(Control[]), i.e. to add a lot of Controlobjects fast to a TableLayoutPanel while still being able to get the position of a Control programmatically?
EDIT to include some information from the comments:
There are up to 1000 controls added
I already use SuspendLayout() and ResumeLayout()
The TableLayoutPanel takes about 2 seconds to load. According to the profiler roughly 50% of the time is spent with adding Controls, 20% with ResumeLayout()
EDIT: MCVE
My original code is more complex, but this is an example of a TableLayoutPanel where adding the controls takes most of the time spent (2/3). I am searching for a way to speed this up.
public class FormTLPTest : Form
{
public FormTLPTest()
{
Height = 800;
Width = 800;
TableLayoutPanel tlp = new TableLayoutPanel();
tlp.Dock = DockStyle.Fill;
tlp.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
tlp.AutoScroll = true;
Controls.Add(tlp);
tlp.ColumnCount = 7;
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 20));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100.0F));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 80));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 30));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 70));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 20));
tlp.SuspendLayout();
for (int i = 0; i<700; i++)
{
Button btn1 = new Button();
Label lb2 = new Label();
Label lb3 = new Label();
Label lb4 = new Label();
TextBox tb5 = new TextBox();
Button btn6 = new Button();
Button btn7 = new Button();
foreach (Control c in new Control[] { btn1, lb2, lb3, lb4, tb5, btn6, btn7})
{
c.Margin = new Padding();
c.Dock = DockStyle.Fill;
c.BackColor = Color.White;
}
btn1.FlatStyle = FlatStyle.Flat;
btn6.FlatStyle = FlatStyle.Flat;
btn7.FlatStyle = FlatStyle.Flat;
btn1.Text = "1";
lb2.Text = "Some longer Text - it contains information. Don't know what I should write to fill the space";
lb3.Text = "Short Text";
lb4.Text = "Short Text";
tb5.Text = "5";
btn6.Text = "Button";
btn7.Text = "+";
tlp.Controls.Add(btn1, 0, i);
tlp.Controls.Add(lb2, 1, i);
tlp.Controls.Add(lb3, 2, i);
tlp.Controls.Add(lb4, 3, i);
tlp.Controls.Add(tb5, 4, i);
tlp.Controls.Add(btn6, 5, i);
tlp.Controls.Add(btn7,6, i);
}
tlp.ResumeLayout();
}
}
Looking at the Reference Source, you can see that Control.ControlCollection.AddRange method is nothing more than a Add loop enclosed in SuspendLayout / ResumeLayout. Since your code is also enclosed with such calls, there should not be a difference in the performance.
TableLayoutPanel does two additional calls - SetRow and SetColumn, so my first thought was that they are the slow parts. However, looking at the the source code (and measuring the time with or w/o those calls), when the layout engine is suspended, their affect on the performance are negligible.
I did some additional tests with your mcve by not using TableLayoutPanel at all and just adding controls to the form itself. The conclusion is - you just have too many controls. mcve is creating 4900 controls. This is too much for WinForms (and Windows in general). After running it, my Windows almost died.
So, the add control performance cannot be improved. But that should not be your main concern. Consider switching to DataGridView or some third party data repeater control which support a lot more number of rows w/o creating significant number of controls.

C# Window Form Application, how to efficiently create a dynamic panel on a window form

I would like to create a few panel dynamically in my window form application. Each panel will consist of 3 labels and one text box and one button. Now I know I can hard code this all at once by declaring each variable every time, but it takes a lot of coding and it is obviously not efficient at all.
So my question is: Is there a way to create pre-define panel dynamically where each time a panel is created will have a predefined layout setup already. So all i need to do is to add a panel, its location and size every time, and all the content(like labels, text-box and button) inside the panel are already setup with their location associated with the panel itself. Do I really have to create a class just for that?
Thanks in advance for read and taking your time.
Create Windows Forms control or user control, see http://msdn.microsoft.com/en-us/library/6hws6h2t.aspx
Create a user control, and place on it whatever you like (your labels). Expose public methods/properties of that control so you can control the contents of it. Place as many of those on the form as you like, they will all look and behave same.
Here is an example you can play with...
for (int i = 1; i < 5; i++)
{
var panel1 = new Panel() { Size = new Size(90, 80), Location = new Point(10, i * 100), BorderStyle = BorderStyle.FixedSingle };
panel1.Controls.Add(new Label() { Text = i.ToString(), Location = new Point(10, 20) });
panel1.Controls.Add(new Label() { Text = i.ToString(), Location = new Point(10, 40) });
panel1.Controls.Add(new Label() { Text = i.ToString(), Location = new Point(10, 60) });
Controls.Add(panel1);
}

Categories