Reordering of controls within a flow layout panel - winforms

I'm having trouble using the flowlayoutPanel in a C# winform application. What I basically have is a flow layout panel that has 3 sections.
Section #1 is a set of 2 controls .. two dropdown controls, they are always in the same order, always visible in all instances
Section #2 is a set of 5 different controls ... based on a series of factors, 1 of the 5 controls is made visible, all others have the Visible propert set to false
Section #3 is a set of 3 controls .. like Section #1 they are always in the same order and always visible.
So what this boils down to is that Section #2 is variable, the others are static.
The problem comes with Section #2 ... when I change the visibility of any of the controls they appear just fine (I.E. ... Section 1 then Section 2 then Section 3) ... EXCEPT when I set the combobox control to be Visible .... in that case, and ONLY in that case .. the order becomes (Section 1 then Section 3 then Section 2) ... I can't figure out what would cause the ordering to be out of sync in just that case.
What I basically do at the beginning of my method is set ALL controls to Visible = false ... then I set Section 1 Visible = true ... then loop through the conditions of Section 2 and set the appropriate controls Visible = true and finally set Section 3 controls Visible = true.
Does anyone have any experience with the flow layout panel control ordering? I can't figure out what is happening for the ComboBox.

Inside FlowLayoutPanel.Controls is a method function called SetChildIndex(Control c, int index) which allows you to set an object to a specific index.
Since FlowLayoutPanel uses control's indices to determine which order to draw them in, you can set this to whichever control's index you are wanting to swap with, and it will bump that controls index up by one, and every one after that.
Here is snippet from my blog of reordering PictureBoxes in a FlowLayoutPanel.
Add a FlowLayoutPanel on a WinForm named flowLayoutPanel1:
public partial class TestForm: Form
{
public TestForm()
{
InitializeComponent();
this.flowLayoutPanel1.AllowDrop = true
}
private void AddImageToBlog(System.Drawing.Image image)
{
PictureBox pbox = new PictureBox();
pbox.SizeMode = PictureBoxSizeMode.Zoom;
pbox.Height = (_picturebox_height * _ScaleFactor);
pbox.Width = (_picturebox_width * _ScaleFactor);
pbox.Visible = true;
pbox.Image = image;
pbox.MouseDown += new MouseEventHandler(pbox_MouseDown);
pbox.DragOver += new DragEventHandler(pbox_DragOver);
pbox.AllowDrop = true;
flpNewBlog.Controls.Add(pbox);
}
void pbox_DragOver(object sender, DragEventArgs e)
{
base.OnDragOver(e);
// is another dragable
if (e.Data.GetData(typeof(PictureBox)) != null)
{
FlowLayoutPanel p = (FlowLayoutPanel)(sender as PictureBox).Parent;
//Current Position
int myIndex = p.Controls.GetChildIndex((sender as PictureBox));
//Dragged to control to location of next picturebox
PictureBox q = (PictureBox) e.Data.GetData(typeof(PictureBox));
p.Controls.SetChildIndex(q, myIndex);
}
}
void pbox_MouseDown(object sender, MouseEventArgs e)
{
base.OnMouseDown(e);
DoDragDrop(sender, DragDropEffects.All);
}
}

Might it be easier to drop another flowlayoutpanel in for section 2, then drop your section 2 controls into that? That way, the visible controls in your top panel never change and you won't have to worry about ordering.

You can reorder controls on flowpanel, changing parent property of controls and reassigning parent property with the order that you need.

Try this generic solution where you can sort you controls according to a property in the user control.
// When adding and removing controls, the order is not kept.
var runsOrderedByStartDate = this.nodesFlowLayoutPanel.Controls.Cast<RunNodeControl>().Select(_ => new { StartDate = _.StartDateTime, RunControl = _ }).OrderBy(_ => _.StartDate).ToList();
// Sets index of controls according to their index in the ordered collection
foreach (var anonKeyValue in runsOrderedByStartDate)
{
this.nodesFlowLayoutPanel.Controls.SetChildIndex(anonKeyValue.RunControl, runsOrderedByStartDate.IndexOf(anonKeyValue));
}

SetChildIndex does not reset the order of the controls in the flowlayout panel. So when we perform FlowLayoutPanel.GetNextControl(q, true) the output is not correct.

For basic control ordering, the simplest way to control the order of controls in the flowlayoutPanel is to set the flowlayoutPanel TabStop property to true. Set the controls tabstop property to True and set the tab order to the order in which you want the controls to appear.

Related

Is it possible to create a control once and have it generated everytime it is needed?

Say for example you have a stackpanel that you would like to programmatically add buttons to.
The code behind for the button generation and addition to the stackpanel is:
Button button = new Button();
button.Content = "Button";
button.HorizontalAlignment = HorizontalAlignment.Left;
button.Name = "Button" + i
stackPanel1.Children.Add(button);
My question is - Is it possible to generate the button once and have it as some kind of template that can be added to the stackpanel whenever it is needed without going through the generation code again?
In WPF each UIElement can only be the logical child of one control at any given time, see WPF Error: Specified element is already the logical child of another element. Disconnect it first, so no you can't use the same button and add it to another control later on, unless you're sure you've gotten rid of that stackpanel
But, you can do recycling. See Optimizing Performance: Controls. especially if you are willing to override MeasureOverride and ArrangeOverride of you stackpanel
I've actually written such a recycler because I had grids with many controls, and I wanted to implement some kind of virtualizing grid. These are the main methods of my class:
internal class Recycler
{
private UIElementCollection _children;
public Recycler(UIElementCollection children)
{
_children = children;
//You need this because you're not going to remove any
//UIElement from your control without calling this class
}
public void Recycle(UIElement uie)
{
//Keep this element for recycling, remove it from Children
}
public UIElement GiveMeAnElement(Type type)
{
//Return one of the elements you kept of this type, or
//if none remain create one using the default constructor
}
}

dynamic tooltips on Winforms controls

I am looking for the cleanest way to bind the same datasource to a control's tooltip that I am binding to the control itself. For example, I have the line
control.DataBindings.Add(new Binding("EditValue", dataFeatures, "Key", true));
where dataFeatures is of type BindingSource. I repeat similar lines for many controls on a WinForm Form. Some of these controls can adopt values whose text can span a greater text width than what is visible within the control itself. Instead of redesigning the layout of the form to account for the possibility of partially hidden text in some controls in a few situations, I would like to have the tooltip of each control be bound to the same property of the BindingSource as the controls' EditValue or Text property. Is this possible? I can imagine there is a way to do it by hand by handling the EditValueChanged event like I already do for different reasons, but I was hoping there would be a cleaner solution than having to add new lines of code for each control.
Anybody have a suggestion?
Thanks!
0. For DevExpress controls you can just bind DevExpressControl.ToolTip property to the same value:
devExpressControl.DataBindings.Add(new Binding("EditValue", dataFeatures, "Key", true));
devExpressControl.DataBindings.Add(new Binding("ToolTip", dataFeatures, "Key", true, DataSourceUpdateMode.Never));
1. For standard WinForms controls you can use System.Windows.Forms.ToolTip component and its ToolTip.Popup event. For each control set its ToolTip to some value otherwise ToolTip will never appears:
control.DataBindings.Add(new Binding("Text", dataFeatures, "Key", true));
toolTip1.SetToolTip(control, "Some value");
Now you can use ToolTip.Popup event:
private bool _updatingToolTip;
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
if (_updatingToolTip) return;
//Get binding for Text property.
var binding = e.AssociatedControl.DataBindings["Text"];
if (binding == null) return;
//Get binding value.
var manager = binding.BindingManagerBase;
var itemProperty = manager.GetItemProperties().Find(binding.BindingMemberInfo.BindingField, true);
object value = itemProperty.GetValue(manager.Current);
string toolTipText;
if (value == null || string.IsNullOrEmpty(toolTipText = value.ToString()))
{
e.Cancel = true;
return;
}
//Update ToolTip text.
_updatingToolTip = true;
toolTip1.SetToolTip(e.AssociatedControl, toolTipText);
_updatingToolTip = false;
}
You can easily implement dynamic tooltips with the ToolTipController component. Put this component onto the Form, and assign to each editor via the BaseControl.ToolTipController property.
When it is done, you can handle the ToolTipController.BeforeShow event and change the text according to the control state. The active control is passed through the SelectedControl property of the event parameter.

WPF tab index issue

I'm working on an application where a user defines a the controls on a form and can set the tab index of any control. As each control is added to the Grid that comprises the viewable form area, the tab index is set to either 0 (default) or some user-defined tab index. Tabbing through the form works fine until the tabindex of one of the controls is changed at runtime(the index doesn't seem to matter.) After this, tabbing cycles only through some of the controls and in addition, the window menu items are now tab stops(they weren't prior to the tabindex change.) Also, the menu's tab properties aren't bound to any datacontext.
The control that's currently changed is a checkbox, but I'm unable to reproduce the behavior with a simplified layout, so any suggestions would help.
Our "form pages" user controls invisible and beneath the current visible page were never disabled when new ones were pushed on the top. Therefore they were included in the tab indexing behavior causing unwanted side effects.
This helped me get to the bottom of the issue:
void InitializeFocusLogger() {
//debug key logging to make focus bugs easier to track
EventManager.RegisterClassHandler(
typeof(UIElement),
Keyboard.PreviewGotKeyboardFocusEvent,
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
}
string lastID = string.Empty;
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
FrameworkElement control = e.NewFocus as FrameworkElement;
if (control == null) return;
ControlViewModel controlVM = control.DataContext as ControlViewModel;
if (controlVM == null || lastID == controlVM.ID) return;
lastID = controlVM.ID;
Debug.Print("Focused: {0} TabIndex: {1}", controlVM.ID, controlVM.TabIndex);
}

Shared Events between multiple instances of WPF Custom Controls

I have a couple Custom User Controls written in C# using the composite User Control model (i.e. starting with a UserControl) and when used in isolation, I'm not seeing any problems. But when two instances of the same control are used within the same parent, they seem to be stealing each other's events.
Here is an example. I have a control that uses a ListView. On LayoutUpdated event, I am updating the width of the columns to adjust for content. Not that this adds any value to the question, but here's the code of the handler:
var view = transactionListView.View as GridView;
if (view == null) return;
view.Columns[0].Width = view.Columns[0].ActualWidth;
view.Columns[0].Width = double.NaN;
view.Columns[view.Columns.Count - 1].Width = view.Columns[view.Columns.Count - 1].ActualWidth;
view.Columns[view.Columns.Count - 1].Width = double.NaN;
const int dynamicColumnIndex = 2;
try
{
var dynamicColumnWidth = view.Columns.Where((t, index) => dynamicColumnIndex != index)
.Aggregate(transactionListView.ActualWidth, (current, t) => current - t.ActualWidth);
view.Columns[dynamicColumnIndex].Width = dynamicColumnWidth - 25;
}
catch (Exception ex)
{
// log
}
Again, the sample above is just provided to provide some context, but the other controls that I'm referring to contain completely different implementations.
I thought if I provided an x:Name for the ListView within the control, it could help, but it didn't. Then I provided an x:Name for the custom control, when I use it. No difference.
The outcome of when I use this control with the ListView is that the second ListView is the only one that maintains the auto-size on the columns. The first one seems un-impacted.

Define Background Color of Alternating TreeView Rows Based on Visibility

Is there a way in WPF to define the background of alternating visible rows?
I've tried setting the AlternationCount property, but that restarts for every child node which gives a weird look.
Ideally what I would like is to know what the visual index of a given node is. Only expanded nodes are counted.
There was no easy way to do this as WPF creates nested containers for the tree nodes. So as Rachel mentioned, looping through the items seemed to be the way to go. But I didn't want to depart too much from the built in ItemsControl.AlternationIndex attached property as that is the one people would expect. Because it is readonly I had to access it via reflection, but after that things fell into place.
First off, make sure you handle the Loaded, Expanded and Collapsed events of your TreeViewItem. In the event handler find the owning TreeView and do a recursive alternation count set of all visible nodes. I created an extension method to handle it:
public static class AlternationExtensions
{
private static readonly MethodInfo SetAlternationIndexMethod;
static AlternationExtensions()
{
SetAlternationIndexMethod = typeof(ItemsControl).GetMethod(
"SetAlternationIndex", BindingFlags.Static | BindingFlags.NonPublic);
}
public static int SetAlternationIndexRecursively(this ItemsControl control, int firstAlternationIndex)
{
var alternationCount = control.AlternationCount;
if (alternationCount == 0)
{
return 0;
}
foreach (var item in control.Items)
{
var container = control.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (container != null)
{
var nextAlternation = firstAlternationIndex++ % alternationCount;
SetAlternationIndexMethod.Invoke(null, new object[] { container, nextAlternation });
if (container.IsExpanded)
{
firstAlternationIndex = SetAlternationIndexRecursively(container, firstAlternationIndex);
}
}
}
return firstAlternationIndex;
}
}
As you can see it runs through each node and sets the custom alternation index. It checks if the node is expanded and if so continues the count on the child nodes.
Above I mentioned that you have to handle the Loaded event for the TreeViewItem. If you only handle the expanded and collapsed events you won't get the new containers that are created when a node is first opened. So you have to do a new pass when the child node has been created and added to the visual tree.
Something I've done with javascript is create an OnLoaded event for a table which loops through the table rows and if the row is visible, it sets the background color to a nextColor variable, and changes the nextColor variable to the opposite color. That might work here.

Resources