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;
}
}
As per the title, I DO not want that to happen.
I have 2 separate labels and I want to make them flash provided an if statement is met. But whenever either label animates, the second label automatically follows to animate. I tried to test it by just changing the foreground color of the label and it works. The problem seems to occur only when I use the animation. Can anyone please help to spot my problem please. Tnks.
void AlertAnimation(Label label)
{
label.Foreground.BeginAnimation(SolidColorBrush.ColorProperty,
new ColorAnimation
{
To = Colors.Red,
Duration = TimeSpan.FromSeconds(0.1),
AutoReverse = true,
RepeatBehavior = new RepeatBehavior(3)
});
}
void calc()
{
if (maxValue > slider1.Value)
{
AlertAnimation(label10);
//label10.Foreground = Brushes.Red; //it works when I use this line.
}
else if (minValue < slider2.Value)
{
AlertAnimation(label11);
//label11.Foreground = Brushes.Red;
}
}
I'm trying to adapt my app for iOS 7. It's written with Xamarin and C#.
I'm having trouble with extra padding for the left button in the navigationbar.
I have a helper method for rendering my back-button which looks like this:
public static UIBarButtonItem GetBackButton (this UIViewController controller)
{
var backImage = new UIImage ("Images/back.png");
var backButton = new UIButton (UIButtonType.Custom);
backButton.Frame = new RectangleF (0, 0, 44, 44);
backButton.SetImage (backImage, UIControlState.Normal);
backButton.TouchUpInside += (object sender, EventArgs e) => {
var cancelBackNavigation = false;
if (controller is UIViewControllerBase) {
if (((UIViewControllerBase)controller).PrepareNavigateBack () != true) {
cancelBackNavigation = true;
}
}
if (cancelBackNavigation == false) {
controller.NavigationController.PopViewControllerAnimated (true);
}
};
return new UIBarButtonItem (backButton);
}
The navigationbar adds lots of padding before the back-button and making the image inside the back-button look very far away from its real position. The code above works fine in iOS 6.
I don't wanna use ContentEdgeInsets cause it will stretch the image and making it ugly.
Anyone with an idea of what to do?
I tried looking up your issue and found out that first, you need to hide the back button as follows:
NavigationItem.HidesBackButton = true;
Then for setting the button, you need to set it this way:
NavigationItem.BackBarButtonItem = yourButton;
That way, you won`t have the extra indentation.
Also you might find the following question useful:
Make a custom back button for UINavigationController
This is how I setup my NavigationBar
controller.View.BackgroundColor = Theme.BackgroundColor;
controller.NavigationItem.SetHidesBackButton (true, false);
controller.NavigationController.Toolbar.TintColor = Theme.BackgroundColor;
controller.NavigationController.NavigationBar.TintColor = Theme.BackgroundColor;
controller.NavigationController.SetNavigationBarHidden (show == false, false);
controller.NavigationController.NavigationBar.BackgroundColor = Theme.BackgroundColor;
controller.NavigationController.NavigationBar.SetTitleTextAttributes (Theme.NavigationBarTextAttributes);
controller.NavigationController.NavigationBar.Subviews [0].Alpha = 0.01f;
I am trying to syncronize the scrolling of two splitcontainers within a splitpanel control. I have the code below:
Point mPrevPan1Pos = new Point();
Point mPrevPan2Pos = new Point();
void PanelPaint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (splitContainer1.Panel1.AutoScrollPosition != mPrevPan1Pos)
{
splitContainer1.Panel2.AutoScrollPosition = new System.Drawing.Point(-splitContainer1.Panel1.AutoScrollPosition.X, -splitContainer1.Panel1.AutoScrollPosition.Y);
mPrevPan1Pos = splitContainer1.Panel1.AutoScrollPosition;
}
else if (splitContainer1.Panel2.AutoScrollPosition != mPrevPan2Pos)
{
splitContainer1.Panel1.AutoScrollPosition = new System.Drawing.Point(-splitContainer1.Panel2.AutoScrollPosition.X, -splitContainer1.Panel2.AutoScrollPosition.Y);
mPrevPan2Pos = splitContainer1.Panel2.AutoScrollPosition;
}
}
However the AutoScrollPosition is always (0,0). I have AutoScroll enabled for both split containers. Why is this? What can I do to get the scroll position?
It looks like you copied the code from this answer: Scroll 2 panels at the same time
Did you wire up the events:
this.splitContainer1.Panel1.Paint += new PaintEventHandler(PanelPaint);
this.splitContainer1.Panel2.Paint += new PaintEventHandler(PanelPaint);
A ToolStripComboBox is placed after a ToolStripButton and is folowed by another one, which is right-aligned. How do I best set up the ToolStripComboBox to always adjust its length to fill all the space available between the preceeding and the folowing ToolStripButtons?
In past I used to handle a parent resize event, calculate the new length to set based on neighboring elements coordinates and setting the new size. But now, as I am developing a new application, I wonder if there is no better way.
I use the following with great success:
private void toolStrip1_Layout(System.Object sender, System.Windows.Forms.LayoutEventArgs e)
{
int width = toolStrip1.DisplayRectangle.Width;
foreach (ToolStripItem tsi in toolStrip1.Items) {
if (!(tsi == toolStripComboBox1)) {
width -= tsi.Width;
width -= tsi.Margin.Horizontal;
}
}
toolStripComboBox1.Width = Math.Max(0, width - toolStripComboBox1.Margin.Horizontal);
}
The above code does not suffer from the disapearing control problem.
There's no automatic layout option for this. But you can easily do it by implementing the ToolStrip.Resize event. This worked well:
private void toolStrip1_Resize(object sender, EventArgs e) {
toolStripComboBox1.Width = toolStripComboBox2.Bounds.Left - toolStripButton1.Bounds.Right - 4;
}
protected override void OnLoad(EventArgs e) {
toolStrip1_Resize(this, e);
}
Be sure to set the TSCB's AutoResize property to False or it won't work.
ToolStrip ts = new ToolStrip();
ToolStripComboBox comboBox = new TooLStripComboBox();
comboBox.Dock = DockStyle.Fill;
ts.LayoutStyle = ToolStripLayoutStyle.Table;
((TableLayoutSettings)ts.LayoutSettings).ColumnCount = 1;
((TableLayoutSettings)ts.LayoutSettings).RowCount = 1;
((TableLayoutSettings)ts.LayoutSettings).SetColumnSpan(comboBox,1);
ts.Items.Add(comboBox);
Now the combobox will dock fill correctly. Set Column or Row span accordingly.