When is Control.Visible = true turns out to be false? - c#

I have a C# WinForms project that's very wizard like in its functionality. The individual steps live on a class called StepPanel, which inherits from the Panel control, within the form and those panels are organized in an array.
What I've run into is that when UpdateUI() is called and walks the array, adjusts the wizards step title text for the current step, it makes sure that all of the inactive steps are hidden, and ensures that the active step is visible, in the right spot, and is the right size.
Here's the code:
private void UpdateUI()
{
// If the StepIndex equals the array length, that's our cue
// to exit.
if (StepIndex == Steps.Length)
{
Application.Exit();
return;
}
for (var xx = 0; xx < Steps.Length; xx++)
{
if (xx == StepIndex)
{
if (!String.IsNullOrEmpty(Steps[xx].Title))
{
LabelStepTitle.ForeColor = SystemColors.ControlText;
LabelStepTitle.Text = Steps[xx].Title;
}
else
{
LabelStepTitle.ForeColor = Color.Red;
LabelStepTitle.Text =
Resources.UiWarning_StepTitleNotSet;
}
}
else
{
Steps[xx].Visible = false;
}
}
Steps[StepIndex].Top = 50;
Steps[StepIndex].Left = 168;
Steps[StepIndex].Width = 414;
Steps[StepIndex].Height = 281;
Steps[StepIndex].Visible = true;
SetNavigationButtonState(true);
}
When everything is said and done, Steps[StepIndex].Visible == false.
I'm still perplexed by this behavior because I was working less than 30 minutes ago.

If you set a parent/container control to Visible = false then setting any child controls to Visible = true will have no effect what so ever. The Visible property of the child control will still be false.
I don't know if it's what happens in this case since I don't know the structure of the controls but it seems to be a likely scenario.
To solve this you need to first set the parent/contianer control to Visible = true and THEN the child control(s).

if (xx == StepIndex)
Is only going to be true and the end of the loop unless I am missing something.

There are several possibilities. When you attach a debugger on the line:
SetNavigationButtonState(true);
does Steps[StepIndex].Visible == true? If so, then make sure that StepIndex is actually the index you expected (not off by 1, and not reflecting the "previous" step). If you verify that the correct step is set to true, then you must be updating it somewhere else.
if Steps[StepIndex].Visible == false right after you set it to true, then either the getter on the Visible property is returning based on some calculation or an event was triggered that is changing it back to false.
HTH.

I encountered same issue using MDIForm in VB.net and Sani Singh Huttunen explanation is correct for me.
I post this answer to give more explanations and also a specific solution or a work around.
When I click on a specific menu to load a new MDI Child form, my program excute following code
Dim frm As New FrmPaiement
frm.MdiParent = Me
Call frm.NewRecord()
Call ReorganizeControlTopPositions(frm.DataPanel)
frm.Show()
where FrmPaiement is a Form class that originally contains a lot of controls and DataPanel is central panel that contains all data TextBox, CheckBox, ComboBox and DateBox controls.
But these time, some controls are hidden in frm.NewRecord() function.
ReorganizeControlTopPosition() function is called to reduce gaps between remainding visible controls
For information, VB.Net code of ReorganizeControlTopPositions() is following
Public Sub ReorganizeControlTopPositions(ctlContainer As Panel)
'Put all controls in a List(Of Control) and sort it on Top position
For Each ctl As Control In ctlContainer.Controls
lstControls.Add(ctl)
Next
lstControls.Sort(Function(x, y) x.Top.CompareTo(y.Top))
'Reduce gaps between 2 visibles controls
Dim iLastTop As Integer = -1
Dim nInvisible = 0
Dim iLastControlTop As Integer = 0
For Each ctl In lstControls
If nInvisible > 0 Then
If ctl.Visible Then
If ctl.Top = iLastControlTop Then
ctl.Top = iLastTop
Else
iLastControlTop = ctl.Top
ctl.Top = iLastTop + 32
End If
End If
End If
If ctl.Visible Then
iLastTop = ctl.Top
Else
nInvisible += 1
End If
Next
End Sub
EXPLANATION OF ERROR
Since ReorganizeControlTopPositions() function is called before frm.Show() function, MDI Child is hidden and in debug mode, ctl.Visible contains always False !
If initialisation code if changed so that ReorganizeControlTopPositions() is called after frm.Show() call, program runs correctly and ctl.Visible contains "correct" value.
Dim frm As New FrmPaiement
frm.MdiParent = Me
Call frm.NewRecord()
frm.Show()
Call ReorganizeControlTopPositions(frm.DataPanel)
The only problem is that MDI Form is shortly (very quicly) displaying controls with gaps and a micro secund later without gaps.
The problem is linked to Microsoft implementation of Visible property.
Setting Visible property seems to change visible value of control but getting Visible value return only True if Visible property of control is True and also all containers containing this control are visibles !
What is written on Control.Visible property on learn.microsoft.com is extremly confuse !!!
Return True if the control and all its child controls are displayed; otherwise, false. The default is true.
The correct definition sould be
Return True if the control and all its PARENT controls are displayed; otherwise, false. The default is true.

Related

DesignerHost fails to create control with Visible = false

Good day!
I'm writing a .vsix to replace old controls to new ones. I have designerHost which is the current designer instance. Then I start converting like this:
foreach (OldCombo oldCmbx in OldCmbxs())
{
...
NewCombo newCmbx = designerHost.CreateComponent(NewComboType, oldcmbx.Name) as NewCmbx;
...
newCmbx.Visible = oldCmbx.Visible;
...
designerHost.DestroyComponent(oldCmbx);
}
The thing is -oldCmbx is always Visible=true, no matter how it's written in the designer.cs file. I'm always creating Visible=true newCmbx's. If I force newCmbx to be Visible=false, then designer doesn't show newCmbx after the conversion, but the visible property is still true, so Visible property is definitely not what I'm searching for. So how can I force newCmbx's to be Visible=false in designer.cs?
After digging through .NET source code I've found that ControlDesigner is shadowing Visible property of the Control, so what is going to be serialized/deserialized in InitializeComponent is far related from actual Visible property of Control.
Designer.Visible property is initialized like this:
public override void Initialize(IComponent component)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(component.GetType());
PropertyDescriptor descriptor = properties["Visible"];
if (((descriptor == null) || (descriptor.PropertyType != typeof(bool))) || !descriptor.ShouldSerializeValue(component))
{
this.Visible = true;
}
else
{
this.Visible = (bool) descriptor.GetValue(component);
}
...
}
descriptor.ShouldSerializeValue(component) for Control.Visible is always false in case of newly created control.
Designer.Visible property:
private bool Visible
{
get
{
return (bool) base.ShadowProperties["Visible"];
}
set
{
base.ShadowProperties["Visible"] = value;
}
}
In the Designer.PreFilterProperties() actual Visible property of the Control is shadowed by Visible property of the designer.
Now, when the designer is initialized(in my code that's happening when I'm creating component designerHost.CreateComponent) newCmbx.Visible is always true.
Why it is so? Because Visible property of the Control is used in painting of the control(on the designer surface as well). If I set newCmbx.Visible = false it just disappears from the design surface (but still serializes from the Visible property of the designer) - that's bad, so by design of the Control class, when Control is instantiated, it is always Visible so that it could be visible on the design surface. Any subsequent changes in Visible property influence Visible property of the designer, not Control itself (in the context of working in Designer mode).
So, what I need in order to solve that problem is Visible property of the designer.
Correct code looks like this:
foreach (OldCombo oldCmbx in OldCmbxs())
{
bool _visible = GetVisiblePropThroughReflection(designerHost.GetDesigner(oldCmbx));
...
NewCombo newCmbx = designerHost.CreateComponent(NewComboType, oldcmbx.Name) as NewCmbx;
...
SetVisiblePropThroughReflection(designerHost.GetDesigner(newCmbx), _visible);
...
designerHost.DestroyComponent(oldCmbx);
}

button over button

I have a problem in my winform c# project.
In my project I have two main functions, one makes buttons at run time and the other function allows me to move the button on the form at run time. Now what can I do if I have button on other button so I made function that replace the button places as it was at the beginning but the function make problems if someone can help me it will be great!
public void upandunder(Button cBtn1, Button cBtn2)
{
if ((cBtn1.Location.X == cBtn2.Location.X) && (cBtn1.Location.Y == cBtn2.Location.Y))
{
int placex = cBtn1.Location.X;
int placey = cBtn1.Location.Y;
cBtn1.Location.X = cBtn2.Location.Y;
cBtn1.Location.Y = cBtn2.Location.Y;
cBtn2.Location.X = placex;
cBtn2.Location.Y = placey;
}
}
its makes me that errorError 1 Cannot modify the return value of 'System.Windows.Forms.Control.Location' because it is not a variable
Correct, the return value of the Location property is not editable. According to the documentation:
Because the Point class is a value type (Structure in Visual Basic, struct in Visual C#), it is returned by value, meaning accessing the property returns a copy of the upper-left point of the control. So, adjusting the X or Y properties of the Point returned from this property will not affect the Left, Right, Top, or Bottom property values of the control. To adjust these properties set each property value individually, or set the Location property with a new Point.
Therefore, you need to rewrite your code to the following:
(Also, I strongly recommend naming the parameters something other than x and y, since you're dealing with coordinates that have x and y values within the function...)
public void upandunder(Button btn1, Button btn2)
{
if ((btn1.Location.X == btn2.Location.X) && (btn1.Location.Y == btn2.Location.Y))
{
Point originalLocation = btn1.Location;
btn1.Location = btn2.Location;
btn2.Location = originalLocation;
}
}
or even better, just compare the two Point values as returned by the Location property (the Point structure overloads the == operator):
public void upandunder(Button btn1, Button btn2)
{
if (btn1.Location == btn2.Location)
{
Point originalLocation = btn1.Location;
btn1.Location = btn2.Location;
btn2.Location = originalLocation;
}
}
Of course, I fail to see how that accomplishes anything. First you check to see that the buttons are positioned on top of each other (have exactly the same x- and y-coordinates), and then if they do, you swap their positions. They're already in the same positions—you tested that before you executed the swapping code.
Judging by the name of your function (upandunder, which should be UpAndUnder following standard .NET naming conventions), it seems as if you wish to change the Z order of the buttons. If that's the case, then you should call either the BringToFront or SendToBack methods of the button control.
The Location Property on a Control returns a Point. The Point structure has the X and Y values you're working with. Instead of accessing them directly, I think you want to provide new Location points.
Give this a try (It works on my Machine)
public void UpAndUnder(Button cBtn1, Button cBtn2)
{
if (cBtn1.Location == cBtn2.Location.Y)
{
Point oldPoint = new Point(cBtn1.Location.X, cBtn1.Location.Y);
cBtn1.Location = new Point(cBtn2.Location.X, cBtn2.Location.Y);
cBtn2.Location = oldPoint;
}
}
If you want to place one button over other, just call
button1.BringToFront();
this will change Z-order of button1 and place it over all other controls.

Docking mdi controls in c#

I have a main form in a panel on the left thats clickable, depending on what you click a new type of form opens. on the righti have another panel where i want to dock the forms that have been opened from clicking on the left.
How can i get the forms to add in a list under one another in the panel on the right? the issue with the code below is that it adds the first element fine. However when i add the second element they both dissapear behind the panel :/
private void addToPanel2(Form o)
{
if (o is Form)
{
if (panel2.Controls.Count == 0)
{
o.MdiParent = this;
panel2.Controls.Add(o);
o.Dock = DockStyle.Top;
o.Show();
}
else
{
//then we know that this is an addable data item
foreach (Form obj in panel2.Controls)
{
if(obj.GetType().Name.Equals(o.GetType().Name))
{
//we dont want to add it as the data type is already open
MessageBox.Show("This data item must already be open. Please Check.");
}
else
{
// add it as its not in there
Form f = (Form)obj;
f.MdiParent = this;
f.Dock = DockStyle.Top;
f.Show();
}
}
}
}
thanks
This is not possible, an MDI child form cannot be a child control of a panel. Adding a non-MDI form to a panel is an iffy proposition as well but is supported. Call its SetTopLevel() method, passing false, set its Visible property to true. You also have to set its FormBorderStyle property to None, it no longer behaves properly as a top-level window.
This just turns it into a UserControl. You are better off actually making it a UserControl, that uses a lot less resources and is much better documented.

Resize Panel according to Label size

I have a Panel with a Label inside.
Sometimes, the Label is very long and the panel must be resized.
I have set the Autosize property to true for both controls, but ....
Can you help me please ?
You also must set AutoSize to true for the containing containers as well, up to the window.
My last attempt in doing so involved quite a bit of redesigning the form with TableLayoutPanel and the like since Dock/Anchor and AutoSize don't seem to mix well.
I have set the Autosize property to true for both controls, but ....
I can tell from the "but" what you are asking for. That's the AutoEllipsis property of the Label. Set it to true and set the MaximumSize property so the label cannot get bigger than its container. The user will see ... so she'll realize the text is truncated. She'll hover the mouse over the label to get a tooltip with the full text.
Letting everything grow to accommodate a label is drastically impractical. You typically can manipulate MaximumSize to let it grow vertically for a while, up to a point.
I encountered a similar problem, and here is a code for you. Assuming your Panel is anchored to the form (top,left,bottom,right), it is the form that needs to be resized, not the Panel.
public static void FitPanel(Panel pnl)
{
int maxright = 0;
int maxbottom = 0;
foreach (Control ctl in pnl.Controls)
{
maxright = (ctl.Right > maxright ? ctl.Right : maxright);
maxbottom = (ctl.Bottom > maxbottom ? ctl.Bottom : maxbottom);
}
int deltabottom = pnl.Bottom - (pnl.Top + maxbottom);
int deltaright = pnl.Right - (pnl.Left + maxright);
Form frm = pnl.FindForm();
frm.SuspendLayout();
frm.Height = frm.Height - deltabottom;
frm.Width = frm.Width - deltaright;
frm.ResumeLayout();
}

How can I set the position of my datagrid scrollbar in my winforms app?

In my C# winforms app, I have a datagrid. When the datagrid reloads, I want to set the scrollbar back to where the user had it set. How can I do this?
EDIT: I'm using the old winforms DataGrid control, not the newer DataGridView
You don't actually interact directly with the scrollbar, rather you set the FirstDisplayedScrollingRowIndex. So before it reloads, capture that index, once it's reloaded, reset it to that index.
EDIT: Good point in the comment. If you're using a DataGridView then this will work. If you're using the old DataGrid then the easiest way to do that is to inherit from it. See here: Linkage
The DataGrid has a protected GridVScrolled method that can be used to scroll the grid to a specific row. To use it, derive a new grid from the DataGrid and add a ScrollToRow method.
C# code
public void ScrollToRow(int theRow)
{
//
// Expose the protected GridVScrolled method allowing you
// to programmatically scroll the grid to a particular row.
//
if (DataSource != null)
{
GridVScrolled(this, new ScrollEventArgs(ScrollEventType.LargeIncrement, theRow));
}
}
Yep, definitely FirstDisplayedScrollingRowIndex. You'll need to capture this value after some user interaction, and then after the grid reloads you'll want to set it back to the old value.
For instance, if the reload is triggered by the click of a button, then in the button click handler, you might want to have as your first line a command that places this value into a variable:
// Get current user scroll position
int scrollPosition = myGridView.FirstDisplayedScrollingRowIndex;
// Do some work
...
// Rebind the grid and reset scrolling
myGridView.DataBind;
myGridView.FirstDisplayedScrollingRowIndex = scrollPosition;
Store your vertical and horizontal scroll values into some variable and reset them.
int v= dataGridView1.VerticalScrollingOffset ;
int h= dataGridView1.HorizontalScrollingOffset ;
//...reload
dataGridView1.VerticalScrollingOffset = v;
dataGridView1.HorizontalScrollingOffset =h;
you can save scroll position with next code
int Scroll;
void DataGridView1Scroll(object sender, ScrollEventArgs e)
{
Scroll = dataGridView1.VerticalScrollingOffset;
}
and you can set scroll of dgv to same position after refresing, load dgv... with next code:
PropertyInfo verticalOffset = dataGridView1.GetType().GetProperty("VerticalOffset", BindingFlags.NonPublic |
BindingFlags.Instance);
verticalOffset.SetValue(this.dataGridView1, Scroll, null);
Just posted the answer on the link given by BFree
The DataGrid has a protected GridVScrolled method that can be used to scroll the grid to a specific row. To use it, derive a new grid from the DataGrid and add a ScrollToRow method.
C# code
public void ScrollToRow(int theRow)
{
//
// Expose the protected GridVScrolled method allowing you
// to programmatically scroll the grid to a particular row.
//
if (DataSource != null)
{
GridVScrolled(this, new ScrollEventArgs(ScrollEventType.LargeIncrement, theRow));
}
}
VB.NET code
Public Sub ScrollToRow(ByVal theRow As Integer)
'
' Expose the protected GridVScrolled method allowing you
' to programmatically scroll the grid to a particular row.
'
On Error Resume Next
If Not DataSource Is Nothing Then
GridVScrolled(Me, New ScrollEventArgs(ScrollEventType.LargeIncrement, theRow))
End If
End Sub
I used the answer by #BFree, but also needed to capture the first visible row in the DataGrid:
int indexOfTopMostRow = HitTest(dataGrid.RowHeaderWidth + 10,
dataGrid.PreferredRowHeight + 10).Row;
Even though this is an old question, Many of the solutions above did not work for me. What worked ultimately was:
if(gridEmployees.FirstDisplayedScrollingRowIndex != -1) gridEmployees.FirstDisplayedScrollingRowIndex = 0;

Categories

Resources