Where to start generating buttons on WPF form - c#

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.

Related

Change background colour of button after multiple buttons are clicked

I am using Visual C# 2010
I have a windows form with about 24 buttons plus a 'Finish' button
Currently the Finish button is red. I want the colour of this button to change to green once all other buttons have been clicked.
When the user clicks button 1 -24 that button changes to green, so I know how to change the background on a click event.
I have a boolean for when each button is clicked.
This is the code I have so far:
if (button1Clicked && button2Clicked ...) // I have button3 through button24 as well
{
finishButton.BackColor = Color.Green;
}
However the button remains Red when all 24 buttons have been clicked.
I have sent the output of button1 to the debug writeline and I know that the boolean changes from false to true when the button is clicked.
I have made the finish button public in the modifiers option.
Am I missing a step? I thought this should have been easy to accomplish.
EDIT
public partial class Form2 : Form
{
public bool button1Clicked = false;
.... // and other buttons are allocated the same way
}
private void button1_Click(object sender, EventArgs e)
{
String textForClipboard = String.Format("{0}_S1_{1}", Form1.dateValue, button1.Text);
Clipboard.SetText(textForClipboard);
button1.BackColor = Color.Green;
button1Clicked = true;
System.Diagnostics.Debug.WriteLine(button1Clicked);
}
EDIT
Where exactly should I have the if loop?
I currently have it in this method
namespace WindowsFormsApplication1
{
public partial class Form2 : Form
{
....
public Form2(Form parentForm)
{
InitializeComponent();
mainForm = parentform;
....
if (button1Clicked)
{
finishedButton.BackColor = Color.Green;
}
}
}
}
This is a quick'n'dirty solution, which however works. It assumes that your 24 buttons are named (not their text) button1 to button24.
1) put your 24 buttons in a GroupBox (e.g. groupBox1)
2) put this in partial class Form2 : Form
bool[] allButtons = new bool[24];
private void button_Click(object sender, EventArgs e)
{
var indexButton = int.Parse((((Button)sender).Name.Substring(6))) - 1;
allButtons[indexButton] = true;
allButtonsClicked();
}
private void allButtonsClicked()
{
const int totalButtons = 23;
int counter = 0;
for (int i = 0; i < totalButtons; i++)
{
if (allButtons[i])
{
counter++;
}
}
if (counter == totalButtons)
{
finishButton.BackColor = Color.Green;
}
}
3) put this in Form2_Load()
foreach(Control ctl in groupBox1.Controls)
{
if (ctl is Button)
{
ctl.Click += button_Click;
}
}
What it does:
It loops through all the controls in groupBox1 when Form2 loads, checks if they are buttons and hooks all 24 buttons click events to button_click(). This way you don't need to declare 24 button clicks, since button_click() will trigger every time each of the 24 buttons is clicked.
We then have an allButtons bool array with 24 positions, all initially set to false. Each time button_click() triggers, we extract the "numerical" portion of the name of the button triggering button_click() at that given time (so for example if it's button1 triggering button_click() at than given time, we take substring "1" from "button1"), convert that to an int (so "1" becomes 1) and use that to set allButtons[0] = true (the array is zero-based, hence why 1 is used for index 0). When all 24 elements of allButtons become true (which is checked in allButtonsClicked()), we change finishButton's color to Color.Green.
A small modification to globetrotter's answer.
Change the allButtonsClicked() to return bool instead of void:
private bool allButtonsClicked()
{
const int totalButtons = 23;
int counter = 0;
for (int i = 0; i < totalButtons; i++)
{
if (allButtons[i])
{
counter++;
}
}
if (counter == totalButtons)
{
return true;
}
return false;
}
So you can then do:
private void button_Click(object sender, EventArgs e)
{
var indexButton = int.Parse((((Button)sender).Name.Substring(6))) - 1;
allButtons[indexButton] = true;
if (allButtonsClicked())
{
finishButton.BackColor = Color.Green;
}
}
This just results in the code being more readable and understandable in every-day language: "Whenever a button is clicked, check if each of the 24 buttons has been clicked at least once. If yes, then change the BackColor of finishButton to Green" and centralises your logic in one place rather than two.
First of all, be sure that the inner part of the condition is executed anytime.
for example you can pop up a message box.
Check only for 3 buttons for first, smaller test cases.
For other test, remove the whole condition and use only the color changing. Is this works?
If not, then you have to play around that part for first.
If it is works, then go back to your condition, that will be the leaker. Check for first only one button clicking. And then two etc.
In that way you can easily find your answer anytime. Do it in small tasks, to find the issue.
Tick a debug point to the condition, and check which flag is false. Mouse over button1clicked etc, all should be true

Handling an array of buttons

I'm creating a simple game where a melody is played, and buttons that corresponds to notes are supposed to be highlighted.
Then the user is to push the button, and during each click the buttons are to highlighted again. I would like to place all the buttons in the GUI graphically.
Can I add a highLight method to the buttons in the GUI? I know I probably could create a new class that ineherits from some button class and create the buttons in the code but I would prefer to do it graphically.
What is the neatest way to handle the button outputs? I know I could paste in code for each button like
private void button_withIndexA(object sender, EventArgs e)
{
checkIfThisNoteWasCorrect();
highLightThisButton();
setHighLightOffForAllOtherButtons();
}
However, I think it would be neater to collect all buttons in some sort of container class and make a function like
class buttonArrayHandler
{
/*constructors etc*/
private void someButtonWasClicked(object sender)
{
/*Check which button was clicked, and do stuff accordingly*/
}
}
However I don't know how to do that. Suggestions?
You could change the button colors to create a highlight effect, but if it's a game you can use images / graphics for buttons, and swap them to another graphic when clicked.
When you double click on a button / graphic / control, by default it creates a method and links it to the click action for you. Instead click on each control, then the little lightning icon, and under the click action, pick the same method for all of them.
Then in your method cast the object sender to get the original control, for example:
var clickedButton = (Button)sender;
Where (Button) may be (Graphics) or whatever type of control you used as a button.
EDIT:
If you need to access a group of controls, you can either keep a global list of names at the top of the form and loop through them:
public List<string> buttonList = new List<string>() { "button1", "button2" };
void SomeMethod()
{
foreach (var controlName in buttonList)
{
this.Controls[controlName].Text = "TEST";
}
}
Or use a fixed name and number range:
void SomeMethod()
{
for (int i = 1; i <= 2; i++)
{
this.Controls["button" + i].Text = "TEST";
}
}
I would recommend adding the buttons programatically just like Carlos487 mentioned, here is the code segment that I made and could work to your advantage:
public Form1()
{
InitializeComponent();
int topMod = 0;
for (int i = 0; i < 5; i++)
{
MakeButton(i,topMod);
topMod += 20;
}
}
public void MakeButton(int index, int margin)
{
Button currentButton = new Button();
currentButton.Text = "Note" + index;
currentButton.Top += margin;
currentButton.Click += OnButtonClick;
panel1.Controls.Add(currentButton);
}
public void OnButtonClick(object sender, EventArgs e)
{
//checkIfThisNoteWasCorrect();
//highLightThisButton();
//setHighLightOffForAllOtherButtons();
MessageBox.Show("My Action was activated!");
}
As you can see you can place the common functions within the OnButtonClick method like I did here, giving all of the buttons the same event sequence. If you do not want to go through the whole process of programming buttons then you could also just do this:
currentButton.Click += OnButtonClick;

How can I get suspendlayout to work when adding controls?

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

How to use "for" to set the background image of a group of buttons in a C# Windows Forms application

I want to use a wheel to change (for example) 30 buttons' background image each time a form loads.
I cannot use this:
for(int i=1;i<=30;i++)
{
button i .backgroundimage=image.fromfile("URL");
}
What should I do?
There are many possible interpretations of your problem. Why can't you use your code? There are also different solutions for your problem.
As example:
public Form1() // Constructor
{
InitializeComponent(); // Ensure all controls are created.
List<Button> buttons = new List<Button>(30);
buttons.Add(mybutton1)
buttons.Add(mybutton2)
// Go futher with all your buttons.
}
private void Form1_Load(object sender, System.EventArgs e) // Create a load event
{
foreach(Button button in buttons)
{
button.BackgroundImage = Image.FromFile(path);
// Note: The file remains locked until the Image is disposed!
}
}
Well you could use something like this assuming this code executes in a Form_Load and the buttons Parent control is your form. Have in mind that you should supply the real path to your image that you want to set as a background image
string path = "rootNameOfTheImage";
int counter = 0;
foreach(Control ctrl in this.Controls)
{
if(ctrl is Button)
{
Button btn = (Button)ctrl;
if(/* test if this button should be used */)
{
btn.BackgroundImage=Image.FromFile(path + counter++.ToString() + ".jpg");
}
}
}

Determining the page number of an inline element while using FlowDocumentPageViewer?

I have a FlowDocumentPageViewer control in my application that programmatically advances through each block and inline element in a FlowDocument (this is because it's part of a typing application and doing so gives visual cues which tell the user what to type). Each time I change the inline element I'm focused on, I want to check what page the inline element is on, and if it's not on the current page, to navigate to the page it is on.
If this is not possible, please suggest any alternate solutions.
Also, if it matters, every inline element I'm dealing with is a Run element.
Are you just trying to automatically navigate to the page? If so we don't need to know the page number and should be able to just use BringIntoView? I'm assuming you have a reference to the block?
The following code navigates to the page the 301st block is on when the button is pressed
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
FlowDocument fd = new FlowDocument();
for (int i = 0; i < 1000; i++)
{
fd.Blocks.Add(new Paragraph(new Run(i.ToString())));
}
view.Document = fd;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(view.Document as FlowDocument) .Blocks.Skip(300).First().BringIntoView();
}
}
If you really, really want the page number you could do:
var previousPage = view.MasterPageNumber;
(view.Document as FlowDocument) .Blocks.Skip(300).First().BringIntoView();
var pageOfControl = view.MasterPageNumber;
view.GoToPage(previousPage);
It didn't flicker or anything in the test app lol! My mate didn't like that though so he suggested:
var ddp = (DynamicDocumentPaginator)view.Document.DocumentPaginator;
var position = ddp.GetObjectPosition(document.Blocks.Skip(300).First());
var page = ddp.GetPageNumber(position);
Just be aware that it is 0 indexed as opposed to the "lol" method which starts at 1

Categories

Resources