Summary: Hooray clickable buttons!
Below you will find a small command containing an inline function that offers an alternative to VA's native "Get User Input - Choice" functionality. In short, it demonstrates how you can use Windows Forms to create your own UI for retrieving user input. I've attached the associated profile below, and the resulting interface provided looks like this:
Here is the command (mostly for demonstration):
When I say: Alt Get User Input// C# inline function for retrieving user input & preprocessing
Inline C# Function: Display UI for getting input & process result, wait until execution finishes
// Check if user provided relevant input
Begin Text Compare : [~~UserInput] Has Been Set
// Check if text should be spoken
Begin Boolean Compare : [~~SpeakPhrase] Equals True
// Output info to event log
Write [Blue] '{TXT:~~UserInput}' to log
// Speak phrase with text to speech
Say, '{TXT:~~UserInput}'
Else
// Output retrieved information
Write [Blue] 'The result is: {TXT:~~UserInput}' to log
End Condition
Else
// Output info to event log
Write [Red] 'No actionable input provided' to log
End Condition
And here is the inline function (where the real magic happens):
Referenced Assemblies: Microsoft.CSharp.dll; System.dll; System.Core.dll; System.Drawing.dll; System.Windows.Forms.dllusing System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class VAInline
{
[STAThread]
public void main()
{
// Enable (operating system) visual styles for the application
Application.EnableVisualStyles();
// Create an instance of Form1 (while passing the dynamic VA for use by the form) and an associated message loop
Application.Run(new Form1(VA));
}
}
public class Form1 : Form
{
// Initialize string variable for storing user input
public string result = null;
// Create and display Form1
public Form1(dynamic VA)
{
// Set up the form
this.Text = "This is a Windows Form"; // Set the caption bar text of the form
this.FormBorderStyle = FormBorderStyle.FixedDialog; // Define the border style of the form to a dialog box
this.MaximizeBox = false; // Set the MaximizeBox to false to remove the maximize box
this.MinimizeBox = false; // Set the MinimizeBox to false to remove the minimize box
this.StartPosition = FormStartPosition.CenterScreen; // Set the start position of the form to the center of the screen
this.AutoSize = true; // Set form sizing to be automatic
this.AutoSizeMode = AutoSizeMode.GrowAndShrink; // Set the form's autosizing to grow or shrink based on the text
this.MinimumSize = new Size(this.Width, this.Height); // Set the form window to be no smaller than design time size
this.MaximumSize = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); // Set the form window to be no larger than screen size
this.Padding = new Padding(10); // Set the padding around the form's border
int FontSize = 12; // Set the font size used for buttons
int ControlSpacing = 15; // Set the spacing between form controls
this.FormClosing += new FormClosingEventHandler((sender, e) => Form1_FormClosing(sender, e, VA)); // Subscribe to event for the form closing and pass in dynamic VA
// Set up label1
Label label1 = new Label(); // Create the instance of the label
label1.Text = "Here is a label containing text"; // Set the text of the label
label1.Font = new Font(label1.Font.FontFamily, FontSize, FontStyle.Bold); // Apply the predefined font size to the label
label1.AutoSize = true; // Set label sizing to be automatic
label1.TextAlign = ContentAlignment.MiddleCenter; // Align the text within label
this.Controls.Add(label1); // Add label to the form
// Set up button1
Button button1 = new Button(); // Create the instance of the button
button1.Text = "Write to VA Log"; // Set the text of the button
button1.AutoSize = true; // Set button sizing to be automatic
button1.AutoSizeMode = AutoSizeMode.GrowAndShrink; // Set the button's autosizing to grow or shrink based on the text
button1.Font = new Font(button1.Font.FontFamily, FontSize); // Apply the predefined font size to the button
this.Controls.Add(button1); // Add button to the form
button1.Click += new EventHandler((sender, e) => button1_Click(sender, e, VA)); // Subscribe to button click events and pass in dynamic VA
// Set up button2
Button button2 = new Button(); // Create the instance of the button
button2.Text = "Say Something"; // Set the text of the button
button2.AutoSize = true; // Set button sizing to be automatic
button2.AutoSizeMode = AutoSizeMode.GrowAndShrink; // Set the button's autosizing to grow or shrink based on the text
button2.Font = new Font(button2.Font.FontFamily, FontSize); // Apply the predefined font size to the button
this.Controls.Add(button2); // Add button to the form
button2.Click += new EventHandler((sender, e) => button2_Click(sender, e, VA)); // Subscribe to button click events and pass in dynamic VA
// Set up button3
Button button3 = new Button(); // Create the instance of the button
button3.Text = "Select This Option"; // Set the text of the button
button3.AutoSize = true; // Set button sizing to be automatic
button3.AutoSizeMode = AutoSizeMode.GrowAndShrink; // Set the button's autosizing to grow or shrink based on the text
button3.Font = new Font(button3.Font.FontFamily, FontSize); // Apply the predefined font size to the button
this.Controls.Add(button3); // Add button to the form
button3.Click += new EventHandler(button3_Click); // Subscribe to button click events
// Set up button4
Button button4 = new Button(); // Create the instance of the button
button4.Text = "Cancel"; // Set the text of the button
button4.AutoSize = true; // Set button sizing to be automatic
button4.AutoSizeMode = AutoSizeMode.GrowAndShrink; // Set the button's autosizing to grow or shrink based on the text
button4.Font = new Font(button4.Font.FontFamily, FontSize); // Apply the predefined font size to the button
this.Controls.Add(button4); // Add button to the form
button4.Click += new EventHandler((sender, e) => button4_Click(sender, e, VA)); // Subscribe to button click events
this.CancelButton = button4; // Set the cancel button of the form to button4
// Set up label2
Label label2 = new Label(); // Create the instance of the label
label2.Text = "Close form via 'X-out' or press Cancel"; // Set the text of the label
label2.Font = new Font(label2.Font.FontFamily, FontSize, FontStyle.Bold); // Apply the predefined font size to the label
label2.AutoSize = true; // Set label sizing to be automatic
label2.TextAlign = ContentAlignment.MiddleCenter; // Align the text within label
this.Controls.Add(label2); // Add label to the form
// Call function for resizing the form
Functions.ResizeForm(this, ControlSpacing);
// Call function for bringing form into focus
Functions.ShowForm(this);
// Output info to event log
//VA.WriteToLog("Form initialization complete");
}
// Function run when button1 is clicked
private void button1_Click(object sender, EventArgs e, dynamic VA)
{
// Do stuff
VA.WriteToLog("Hello World!", "purple"); // Output to event log
}
// Function run when button2 is clicked
private void button2_Click(object sender, EventArgs e, dynamic VA)
{
// Do stuff
string MyText = "He sells sea shells"; // Initialize string with a text phrase
VA.SetText("~~UserInput", MyText); // Send text phrase back to VoiceAttack as text variable
VA.SetBoolean("~~SpeakPhrase", true); // Set flag indicating that text should be spoken
this.Close(); // Close the form
}
// Function run when button3 is clicked
private void button3_Click(object sender, EventArgs e)
{
// Do stuff
result = "You choose wisely"; // Store result in string variable
this.Close(); // Close the form
}
// Function run when button4 is clicked
private void button4_Click(object sender, EventArgs e, dynamic VA)
{
// Do stuff
VA.WriteToLog("Pressed cancel!", "yellow"); // Send text phrase back to VoiceAttack as text variable
VA.SetText("~~UserInput", "nothing really..."); // Send info back to VoiceAttack as text variable
this.Close(); // Close the form
}
// Function run when the form is closing
private void Form1_FormClosing(object sender, FormClosingEventArgs e, dynamic VA)
{
// Do stuff
if (result != null) // Check if result string contains data
VA.SetText("~~UserInput", result); // Send result back to VoiceAttack as text variable
}
}
public class Functions
{
// Function for resizing the form and positioning all the controls based on their width
public static void ResizeForm(Form f, int ControlSpacing)
{
// Determine the form control with the maximum width, store this width, and set position of this control
int MaxWidth = 0; // Initialize integer variable for storing the MaxWidth
Control MaxWidthControl = null; // Initialize variable for capturing the widest form control
foreach (Control c in f.Controls) // Loop through each control on the form
{
if (c.Size.Width > MaxWidth) // Check if the current control's width is greater than MaxWidth
{
MaxWidthControl = c; // Set MaxWidthControl to current control
MaxWidth = c.Size.Width; // Redefine MaxWidth using current control's width
}
}
MaxWidthControl.Location = new Point(f.Padding.Left, 0); // Set the location of the MaxWidthControl on the form
// Set location of all other controls within the form
Control PreviousControl = null; // Initialize variable for capturing the previous form control
foreach (Control c in f.Controls) // Loop through each control on the form
{
c.Location = new Point(MaxWidth / 2 + f.Padding.Left - c.Size.Width / 2, (PreviousControl == null ? ControlSpacing : PreviousControl.Height + PreviousControl.Top + ControlSpacing)); // Set location of control c
PreviousControl = c; // Store current control as PreviousControl for future processing
}
}
// Function for brining form into focus
public static void ShowForm(Form f)
{
// Retrieve the window handle for the form and give the form focus
IntPtr myHandle = f.Handle;
SetForegroundWindow(myHandle.ToInt32());
}
// External function for bringing window into focus using its handle
[DllImport("User32.dll")]
public static extern Int32 SetForegroundWindow(int hWnd);
}
// References:
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.form?view=netframework-4.7.2
// https://stackoverflow.com/questions/491399/centering-controls-within-a-form-in-net-winforms
// https://stackoverflow.com/questions/5069391/whats-the-purpose-of-the-components-icontainer-generated-by-the-winforms-design
// https://stackoverflow.com/questions/5962595/how-do-you-resize-a-form-to-fit-its-content-automatically
// https://docs.microsoft.com/en-us/dotnet/framework/winforms/how-to-create-a-windows-forms-application-from-the-command-line
// https://social.msdn.microsoft.com/Forums/en-US/147611ae-f5ab-43df-9e44-c9735e4c217c/cnet-form-close-event?forum=Vsexpressvcs
// https://stackoverflow.com/questions/5282588/how-can-i-bring-my-application-window-to-the-front
// https://stackoverflow.com/questions/8644253/pass-parameter-to-eventhandler
Some comments:- From the looks of things, anything you can do with Windows Forms you can do with VA. This is just one example, and you can create whatever UI you prefer.
- From a layout and formatting perspective, this example is designed to create a single column of Buttons and/or Labels (other types of Controls could probably be used as well). The height of each Control is one text line and the corresponding widths should grow and shrink based on the length of the contained text.
- The generated form will be brought into focus once the inline function displays it.
- The 'Write to VA Log' button can be repeatedly pressed without closing the form (UI)
- The other buttons as well as the X-icon for closing the window will cause the form to close (with corresponding additional processing prior to closing where applicable)
- Processing within the inline function, and subsequently its parent command, is paused until the form closes. You can run your own form asynchronously (won't work well in this example) if you wish.
Final comment: the dynamic VAI wanted to briefly talk about the 'VA' object within the inline function. For a while I've not needed to call VA-related functions (VA.ClearLog, VA.WriteToLog, etc.) outside of the VAInline class. However (opinion) Forms are best suited towards having a dedicated class, and the method to access the VA object outside of VAInline may not be obvious (it wasn't for me). You basically have to pass
dynamic VA into classes outside of VAInline, and the provided example demonstrates this further.
Enjoy!