I am adding controls after the shown event on Form. The controls are showing up one at a time despite the fact I called SuspendLayout(). How can I get the layout to suspend so the controls only display when they are all finished loading?
public partial class ControlCreateTest : Form
{
public ControlCreateTest()
{
InitializeComponent();
}
private void AsyncControlCreateTest_Shown(object sender, EventArgs e)
{
CreateControls();
}
private void CreateControls()
{
SuspendLayout();
int startPoint= 0;
for (int i = 0; i < 4; i++)
{
UserControl control = new UserControl() { Text = i.ToString(), Height = 100, Width = 100 };
control.Load += control_Load;
Controls.Add(control);
control.Top = startPoint;
startPoint += control.Height;
}
ResumeLayout();
Text = "Loading complete";
}
void control_Load(object sender, EventArgs e)
{
Thread.Sleep(500);
RichTextBox newRichTextBox = new RichTextBox() { Height = 100, Width = 100 };
UserControl control = sender as UserControl;
control.Controls.Add(newRichTextBox);
newRichTextBox.Text = "loaded";
}
}
UPDATE
It seems that once these forms begin loading...the visibility and suspend calls are thrown out the window immediately. That is quite troublesome when the Load events are long running.
Getting a little hacked at the obscurity of Winforms dev. Anyway...I set the width and height of the form to 1 pixel in the constructor. When show is called I hide the window and put the window back to normal size. It's hard to notice the tiny window before it's hidden.
This lets my routines fire up and loading form display without all the headache.
UPDATE
When using ShowDialogue(), this dumb little trick only works if you Set Visible = true before Form_Shown returns control to the caller. I found that if you set Visible = true in Form.Shown the Closing event will be triggered...man I flipping love WINFORMS....
Try using AddRange to add all your controls at once:
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.controlcollection.addrange.aspx
Related
Update
I accepted Rufus L's answer with a few mods, Relevant code follows
public partial class ClsOfficeRibbonFooTab
{
private void FooTab_Load(object sender, RibbonUIEventArgs e)
{
.
.
.
}
private void CheckResolution()
{
// set the left position so that the expanded version of the form fits on the screen
Screen screen = Screen.FromHandle(new IntPtr(Globals.ThisAddIn.Application.ActiveWindow.Hwnd));
if (screen.Bounds.Width < 1360 || screen.Bounds.Height < 768)
{
throw new FormatException(String.Format("The {0} is supported on screens with a resolution of 1360 by 768 or greater. Your screen is {1} by {2}", "Some caption text", screen.Bounds.Width, screen.Bounds.Height));
}
}
private void ObjButtonFoo_Click(object sender, RibbonControlEventArgs e)
{
using (ClsFormFoo objFormFoo = new ClsFormFoo(parentWindow: Globals.ThisAddIn.Application.ActiveWindow))
{
CheckResolution();
objFormFoo.ShowDialog();
}
}
}
public partial class ClsFormFoo : Form
{
// This form is a fixed dialog with a flyout on the right side.
// expandedWidth is a const set to the max width of this fixed dialog (i.e., the dialog with the flyout extended)
const int expandedWidth = 1345;
public ClsFormFoo(Microsoft.Office.Interop.Word.Window parentWindow)
{
InitializeComponent();
Top = parentWindow.Top;
}
private void ClsFormFoo_Load(object sender, EventArgs e)
{
Screen screen = Screen.FromHandle(new IntPtr(Globals.ThisAddIn.Application.ActiveWindow.Hwnd));
// set the left position so that the expanded version of the form fits on the screen for all legal resolutions
int halfScreenWidth = (int)(screen.WorkingArea.Width / 2);
// This form is a fixed dialog with a flyout on the right side.
// expandedWidth is a const set to the max width of this fixed dialog (i.e., the dialog with the flyout extended)
int halfFormWidth = (int)(expandedWidth / 2);
this.Left = screen.Bounds.Left + ((int)(halfScreenWidth - halfFormWidth));
}
}
Original Post
My VSTO Add-In provides a ribbon button that when clicked, calls ObjButtonFoo_Click, which in turn, shows a ClsFormFoo form (See Code below). ObjButtonFoo_Click includes code to create an IWin32Window owner value representative of Word to pass to ShowDialog.
On a multiple-monitor setup, I would expect that objFormFoo would appear on the same monitor on which Word itself is displayed. However, when I bring up Word on a secondary monitor and cause ObjButtonFoo_Click to be executed, objFormFoo appears on the Primary monitor
What do I do to make objFormFoo appear on the same monitor that Word itself is displayed on whether its the primary monitor or not?
Note: I verified that winWordMain is populated, i.e., its not null. See winWordMain below
Code
private void ObjButtonFoo_Click(object sender, RibbonControlEventArgs e)
{
NativeWindow winWordMain = new NativeWindow();
winWordMain.AssignHandle(new IntPtr(Globals.ThisAddIn.Application.ActiveWindow.Hwnd));
IntPtr(Globals.ThisAddIn.Application.ActiveWindow.Hwnd);
using (ClsFormFoo objFormFoo = new ClsFormFoo()
{
objFormFoo.ShowDialog(winWordMain);
}
winWordMain.ReleaseHandle();
}
winWordMain
I don't have VSTO to test this, but it seems to me that you could just get the position of the ActiveWindow that you're using as the parent, and then use that as a reference for positioning your child form:
private void allRootsWithChilds_CheckedChanged(object sender, EventArgs e)
{
var winWordMain = new NativeWindow();
var parent = Globals.ThisAddIn.Application.ActiveWindow;
winWordMain.AssignHandle(new IntPtr(parent.Hwnd));
using (var objFormFoo = new ClsFormFoo())
{
// Set the Left and Top properties so this form is centered over the parent
objFormFoo.Left = parent.Left + (parent.Width - objFormFoo.Width) / 2;
objFormFoo.Top = parent.Top + (parent.Height - objFormFoo.Height) / 2;
objFormFoo.ShowDialog(winWordMain);
}
winWordMain.ReleaseHandle();
}
You just need to set the StartPosition property of your form to the FormStartPosition.CenterParent value:
loginForm.StartPosition = FormStartPosition.CenterParent;
loginForm.ShowDialog(parentWindowdle);
I used to make a simple screensaver on C#. I used the following code to open the screensaver on the desired screen or on all of them.
You have to take bounds from the desired screen and pass it to the form.
// Constructor
public NameForm(Rectangle bounds)
{
InitializeComponent();
this.Bounds = bounds;
}
// In my case, this is opening the screensaver on all screens
foreach (Screen screen in Screen.AllScreens)
{
NameForm form = new NameForm (screen.Bounds);
form.Show();
}
I have WPF form with Grid and inside that Grid in Row(1).Column(1) i have StackPanel.
Inside that StackPanel i want to generate buttons.
I don't know how many buttons will be generated, since form(with grid and stackPanel) can be of different size.
Code below works, buttons are getting generated if i run that piece of code on Button_Click for example.
But buttons are not generated if I run this piece of code after InitializeComponent().
I guess, that after InitializeComponent WPF form is still not drawn(or finished loading) so my stPanel.ActualHeigh =="0", and since I can't divide with zero nothing acctualy happens.
Can you suggest some workaround, or even better proper solution?
public partial class frmReceipt : Window
{
public frmReceipt()
{
InitializeComponent();
addButtonGrp(); //am i too fast :)
}
private void addButtonGrp()
{
//Calculate size of container to determine numbers of button
int btnMinimumHeightSize = 30;
int btnNumberCreated = (Convert.ToInt16(stPanel.ActualHeight) / btnMinimumHeightSize);
for (int i = 0; i < btnNumberCreated; i++)
{
CreateGroupButtons btn = new CreateGroupButtons();
var btnX = new Button();
btnX = (btn.addButton(i, btnMinimumHeightSize, Convert.ToInt16(stPanel.ActualWidth)));
btnX.Click += ClickHandlerGrp;
if (i == btnNumberCreated - 1)
{
btnX.Height = btnMinimumHeightSize + ((Convert.ToDouble(stPanel.ActualHeight) / btnMinimumHeightSize) % 1) * (btnNumberCreated);
}
stPanel.Children.Add(btnX);
}
}
private void ClickHandlerGrp(object sender, RoutedEventArgs e)
{
var button = sender as Button;
MessageBox.Show("Clicked button number: " + button.Tag);
string test = Convert.ToString(button.Tag);
switch (test)
{
case "PLUGrp":
addButtonGrp(); //this is just for test, i don't want to generate buttons this way
break;
default:
break;
}
}
}
}
Thanks a lot!
I think you're right about running your code before the form has displayed. (It would be easy to check by putting a breakpoint on the for loop)
You can use the Loaded event of the form. Put this in your XAML for the window
Loaded="MainWindowView_OnLoaded"
and this in your C#
private void MainWindowView_OnLoaded(object sender, RoutedEventArgs e)
{
addButtonGrp();
}
This should then fire after the form is displayed, when you know the height of your stack panel.
I have created a UserControl called Toggle, this is my code for it
[DefaultEvent("Click")]
public partial class Toggle : UserControl {
public bool ToggleStatus { get { return toggleStatus; } }
private bool toggleStatus { get; set; }
public Toggle() {
InitializeComponent();
toggleStatus = true;
}
private void toggleClick(object sender, EventArgs e) {
if (toggleStatus) { // currently set as "true" or "on"
this.lblSwitch.Dock = DockStyle.Right;
this.pnlBackground.BackColor = System.Drawing.Color.Red;
toggleStatus = false;
} else { // currently set as "false" or "off"
this.lblSwitch.Dock = DockStyle.Left;
this.pnlBackground.BackColor = System.Drawing.Color.Green;
toggleStatus = true;
}
}
}
The toggleClick method is tied to the click event of controls within the UserControl; this fires off just fine.
However, when I put my Toggle control on a form and attempt to tie an event to the click of it, it won't fire off.
private void toggleSoundClick(object sender, EventArgs e) {
soundToggle = !soundToggle;
}
I've made sure that the proper method is tied to the click event in my Designer.cs file of both my UserControl and my form
UserControl:
this.lblSwitch.Click += new System.EventHandler(this.toggleClick);
this.pnlBackground.Click += new System.EventHandler(this.toggleClick);
(I have it tied to two controls on my Toggle since I want it to fire no matter where you click on the control)
Form:
this.tglSound.Click += new System.EventHandler(this.toggleSoundClick);
The expected behavior for the UserControl is to fire off toggleClick (which it does) then the form should fire off toggleSoundClick (which it doesn't). I have seen this behavior work fine for other UserControls I have designed and used in this same project.
To clarify:
I have a UserControl called ServerDisplay. I have a method tied to the click event of the background panel of ServerDisplay (in the code for ServerDisplay) that shows a random MessageBox:
private void ServerDisplay_Click(object sender, EventArgs e) {
MessageBox.Show("test");
}
Then, I have a ServerDisplay control contained within my form. I have a method tied to the click event of it as well (in the code for my form)
private void serverDisplayClick(object sender, EventArgs e) {
if (loaded) {
ServerDisplay display = (ServerDisplay)sender;
this.lblLastServer.Text = "Last server joined was " + display.Server.Name + " at " + DateTime.Now.ToString("h:mm tt");
centerControl(this.lblLastServer);
}
}
When I click on the ServerDisplay control in my form, it shows the MessageBox (code from within ServerDisplay), then updates the label I specified in the code (code from form). This is the intended behavior, but it is not working for my other UserControl.
I finally figured it out! The way I had the control set up, I had the control itself, a panel filling up the entire background (I used this for the color), and then another panel inside the first panel to act as the "switch".
When I got rid of the first panel and just used the background of the control for the color and a small panel for the switch, it works when I click the background, but not when I click the "switch" panel. I guess this opens up more questions that I'll have to ask separately from this one, but at least I got my answer.
I have a WindowsForm application that has a FlowLayoutPanel container with a TextBox inside.
This control is bigger than the flow panel, I've set the flow panel to AutoScroll = true.
The problem is I don't know how to make the flow panel scroll to the position of the text edition. If I write continuously in the textbox eventually I pass beyond of what it is visible. The scroll remains at the top and I can't see what it is written.
In consequence the question is, how can I make the container react to keep visible what it is being written?
I think I finally did it:
public partial class Form1 : Form
{
public Point InitialTextBoxLoc ;
public Form1()
{
InitializeComponent();
InitialTextBoxLoc = textBox1.Location;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
Point caretLocalLoc = textBox1.GetPositionFromCharIndex(textBox1.Text.Length-1);
Point caretLoc = new Point(caretLocalLoc.X + InitialTextBoxLoc.X,
caretLocalLoc.Y + InitialTextBoxLoc.Y);
Point scrollLoc = flowLayoutPanel1.AutoScrollPosition;
if (caretLoc.X >= flowLayoutPanel1.Size.Width-10)
{
scrollLoc.X = caretLoc.X;
}
if (caretLoc.Y >= flowLayoutPanel1.Size.Height-10)
{
scrollLoc.Y = caretLoc.Y;
}
flowLayoutPanel1.AutoScrollPosition = scrollLoc;
}
}
There is a hacky solution, but if there is nothing better you can try it. First, subscribe for a TextChanged event. Than, on text changed, check in which line is the caret, check what's the height of the line and scroll the flow layout panel to the position of the line.
The hacky bit is basically to get the height of the line. To do that you have to subtract Y coordinate of the second line from the Y coordinate of the first line.
So the code is:
private void textBox1_TextChanged(object sender, EventArgs e)
{
int lineHeight = 0;
if (textBox1.Lines.Count() > 1)
{
Point p1 = textBox1.GetPositionFromCharIndex(textBox1.GetFirstCharIndexFromLine(0));
Point p2 = textBox1.GetPositionFromCharIndex(textBox1.GetFirstCharIndexFromLine(1));
lineHeight = Math.Abs(p1.Y - p2.Y);
}
int lineIndex = textBox1.GetLineFromCharIndex(textBox1.SelectionStart);
flowLayoutPanel1.AutoScrollPosition = new Point(0, lineIndex * lineHeight);
}
I have a Tooltip with the ShowAlways property set to true.
On the controls where I want the tooltip to display (LinkLabels in this instance), I see there is a "ToolTip on <name of my Tooltip>" property which expects a string.
However, my tooltip is shared between 5 LinkLabels, and should differ depending on which one is hovered over.
I do have a shared click event that works:
private void linkLabelPlatypus1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
int Platypus = 1;
LinkLabel ll = null;
if (sender is LinkLabel)
{
ll = sender as LinkLabel;
}
if (null != ll)
{
if (ll.Name == linkLabelPlatypus2.Name)
{
Platypus = 2;
} else if (ll.Name == linkLabelPlatypus3.Name)
{
Platypus = 3;
} else if (ll.Name == linkLabelPlatypus4.Name)
{
Platypus = 4;
} else if (ll.Name == linkLabelPlatypus5.Name)
{
Platypus = 5;
}
toolTipPlatypi.SetToolTip(ll, DuckbillData.GetPlatypusDataForToolTip(Platypus));
}
}
...but I want the tooltips to also show on hover, and not require the user to click the label.
You only need to set the tooltip once :
public Form1()
{
InitializeComponent();
toolTip1.SetToolTip(linkLabel1, "foo");
toolTip1.SetToolTip(linkLabel2, "bar");
}
Done.
Doing this in a MouseHover or MouseEnter handler will call this function over and over each time the event fires. It will work, but it is unnecessarily complicated.
You only need one ToolTip on a form to provide tips for any number of components and it can provide them all simultaneously and continuously (ie: you don't have to change it or set it each time). Each component can have only one tip, but you can change it throughout the program any time you like. ShowAlways does not have to be true - it is used to make tooltips show on forms which are not active (ie: hover over an inactive window behind one with focus, etc).
You should write a event handler for Mouse Hover and have your tool tip display logic inside it.
private void Label1_MouseHover(object sender, System.EventArgs e)
{
//display logic
}
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.mousehover.aspx