Adding events to rich textbox components - c#

I'm trying to add events to a label that I create every time in a rich textbox. Here is the code below.
In my class MainWindowController I have:
public static Window MainWindow = new Windows.MainWindow();
public static Pages.MainPage MainPage = new Pages.MainPage();
public static Pages.LoginPage LoginPage = new Pages.LoginPage();
public static FlowDocument ChatDocument = new FlowDocument();
public static Paragraph ChatParagraph = new Paragraph();
And in another class I have this method and I call it each time the client receives an update status message through the network.
private static void HandleStatus(string MessageString)
{
string Sender = AuxiliaryClientWorker.GetElement(MessageString, "-U ", " -Content");
string Message = AuxiliaryClientWorker.GetElement(MessageString, "-Content ", ".");
MainWindowController.MainWindow.Dispatcher.Invoke(() =>
{
#region Status updater
System.Windows.Controls.Label StatusLabel = new System.Windows.Controls.Label();
StatusLabel.FontSize = 18;
StatusLabel.FontStyle = FontStyles.Oblique;
StatusLabel.FontStyle = FontStyles.Italic;
StatusLabel.Foreground = System.Windows.Media.Brushes.Black;
StatusLabel.Content = Sender + " has updated their status to " + Message;
StatusLabel.MouseEnter += StatusLabel_MouseEnter;
#endregion
MainWindowController.ChatParagraph.Inlines.Add(StatusLabel);
MainWindowController.ChatParagraph.Inlines.Add(Environment.NewLine);
MainWindowController.ChatDocument.Blocks.Add(MainWindowController.ChatParagraph);
MainWindowController.MainPage.ClientChatTextBox.Document = MainWindowController.ChatDocument;
});
}
private static void StatusLabel_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
MessageBox.Show("This");
}
Everything works fine, the only problem is that the UI elements within the rich textbox do not "listen" for events, henceforth the events never fire. The MessageBox never shows. I tried adding the Dispatcher.Invoke command to the method below and it just refuses to work and the event never fires. I hope you all understand what I'm trying to do!
TLDR: StatusLabel_MouseEnter never fires

You need to set the IsDocumentEnabled property to true on your RichTextBox.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.richtextbox.isdocumentenabled?view=netcore-3.1
Gets or sets a value that indicates whether the user can interact with UIElement and ContentElement objects within the RichTextBox.
Events from a richtextbox document are kind of tricky.
You might also have to put a control in a BlockUIContainer for some events.
Even then, some things won't quite work as you might expect.
Clicking already has meaning so you will likely find it already handling click events. Use preview events - PreviewMouseDown and PreviewMouseUp for that sort of thing.

Related

Trigger button click event while executing thread

I am executing a thread that runs a C# method. During the execution of the method I have set some checkpoints to update the UI of the MainWindow.
The update is about triggering a button click placed in the MainWindow.
The button click c# code
private void ProjectLogsButton_Click(object sender, RoutedEventArgs e)
{
//table's name
TextBlock1.Text = GetTableName(SQLServerConnectionDetails(), "LOG");
//calling an sql query to fetch data to datagrid
PullDataFiles(SQLServerConnectionDetails(), HomePageTab.Header.ToString().Split(" - ")[1], GetTableName(SQLServerConnectionDetails(), "LOG"), datagrid, 1);
}
}
To tackle so far this trivial problem, for which I don't have a clue why is not working, I have approached two different ways:
Approach 1 - Using IProgress
public class UpdateUI
{
public Button ButtonLogs { get; set; }
}
public void RunCalculationsMethod(string connectionstring, string foldername, string delimeter, Button button_click, IProgress<UpdateUI> progress_ui)
{
try
{
using (SqlConnection sqlConnection = new SqlConnection(connectionstring))
{
sqlConnection.Open();
var calculations_query_proc1 = RunProcedureCalculations("EXEC [dbo].[proc1]", foldername, delimeter, 0);
using SqlCommand sqlCommand_proc1 = new SqlCommand(calculations_query_proc1, sqlConnection);
{
Debug.WriteLine(String.Format("Executing: {0}", calculations_query_proc1));
sqlCommand_proc1.CommandTimeout = 60 * 5;
sqlCommand_proc1.ExecuteNonQuery();
}
progress_ui.Report(new UpdateUI {ButtonLogs = button_click});
Thread.Sleep(10000); //I just freeze the thread to check if the button is triggered
}
}
}
public void UpdateProgressUI(Button ButtonViewLogs)
{
ButtonViewLogs = ProjectLogsButton;
ButtonViewLogs.Click += new RoutedEventHandler(this.ProjectLogsButton_Click);
}
private async void RunCalculationsButton_Click(object sender, RoutedEventArgs e)
{
IProgress<UpdateUI> pg_ui = new Progress<UpdateUI>(pg_ui => UpdateProgressUI(pg_ui.ButtonLogs));
//Update the UI
string getconnectionstring = SQLServerConnectionDetails();
TextBlock sqltextcalculation = SQLSuccessfulTextCalculations;
Button projectlogs = ProjectLogsButton;
await Task.Run(() => RunCalculationsMethod(getconnectionstring, String.Format("C:\\folder_path\\"), ";", projectlogs, pg_ui));
}
Approach 2 - Using Application.Current.Dispatcher.Invoke
public void RunCalculationsMethod(string connectionstring, string foldername, string delimeter, Button button_click, IProgress<UpdateUI> progress_ui)
{
//Code as posted above
Application.Current.Dispatcher.Invoke(new Action(() => button_click.Click += new RoutedEventHandler(this.ProjectLogsButton_Click)));
//The rest of the code
}
None of the two approaches can successfully trigger the button click ProjectLogsButton_Click. I literally have no clue why none of the two approaches may apply. One of my concerns is that ProjectLogsButton_Click calls methods like SQLServerConnectionDetails() that it is out of the thread. But if this was the case, I guess that VS 2019 would have rained an error.
The line below:
ButtonViewLogs.Click += new RoutedEventHandler(this.ProjectLogsButton_Click)
...attaches an event handler to the Click event of the ButtonViewLogs button. It doesn't trigger the event. To trigger the event you need code like this:
ButtonViewLogs.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
This will trigger the event, but nothing will happen if there is no handler attached. So you need to have both. You need to have a handler attached, and then you need to trigger the event.
Attaching the handler needs to be done only once, and it is usually done during the initialization stage of the Window. If you attach it more than once, the handler will also be invoked multiple times per event, and you rarely want that. If you are using a visual designer, attaching the handler is normally done automagically by the designer. Your responsibility is just to write the code inside the handler.
Triggering the event can happen any number of times. You can trigger the event using the Progress class like this:
var progress = new Progress<Button>(
button => button.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)));
//...
progress.Report(ButtonViewLogs);

Load popup form cross thread

I'm having trouble manipulating forms when from another thread.
I've overcome the issue by loading the form at runtime, showing it then hiding it. This means the form is created on the right thread and can be manipulated using invokes.
This is not the right way to do it. I have 3 problems that come from using this method
I can't spawn another popup box I have to use the one I created at runtime
The forms flash briefly on load - now that I have 3 forms its pretty obvious what I'm doing.
I have to use a variable bool to hold if the popup is open or not.
If anyone could point me in the right direction It would be much appreciated. Currently my code looks like:
On Main form Load:
CallerIDfrm = new frmCallerID();
CallerIDfrm.Show();
CallerIDfrm.Hide();
to manipulate the popup Im using
delegate void StringArgReturningVoidDelegate1(string CallerIDnum, string CallerIDname, string ContactID);
private void CallerID(string CallerIDnum, string CallerIDname, string ContactID)
{
if (CallerIDfrm.InvokeRequired)
{
StringArgReturningVoidDelegate1 d = new StringArgReturningVoidDelegate1(CallerID);
CallerIDfrm.Invoke(d, new object[] { CallerIDnum, CallerIDname, ContactID });
}
else
{
if (ContactID != null || ContactID != "0")
{
CallerIDfrm.ContactID = ContactID;
}
CallerIDfrm.Mainfrm = this;
CallerIDfrm.TopLevel = true;
CallerIDfrm.TopMost = true;
CallerIDfrm.lblCallerIDname.Text = CallerIDname;
CallerIDfrm.lblCallerIDnum.Text = CallerIDnum;
CallerIDfrm.Show();
CallerIDOpen = true;
}
}
To Hide the popup until required again im using:
delegate void StringArgReturningVoidDelegate2();
private void CallerIDClose()
{
if (CallerIDfrm.InvokeRequired)
{
StringArgReturningVoidDelegate2 d = new StringArgReturningVoidDelegate2(CallerIDClose);
CallerIDfrm.Invoke(d, new object[] { });
}
else
{
try
{
CallerIDfrm.Hide();
CallerIDOpen = false;
}
catch
{
}
}
}
I've tried otherways but the Popup loads as if it is not responding and I loose access to the popup.
Ultimately I'd like to be able to spawn multiple popups and have the ability to close them from the Main Form.
What I gather from your question: You have an caller api/lib/class and you like to show CallerId on a popup form when a call is received. Have a look at Events and Event Driven programming.
The following codes has not been tested, I wrote it from top of my head. Might not compile, they are here to show an example:
Create an CallReceived event in api/lib class as follows:
public event EventHandler<CallReceivedEventArgs> CallReceived;
protected void OnCallReceived(EventArgs e)
{
var handler = CallReceived;
if (handler != null)
handler(this, e);
// Note: For C# 6.0 and later, above statements can be simplified to
// CallReceived?.Invoke(this, e);
}
Note: If you don't have access to this api/lib code, create a Gateway class and put your event in there along with mechanism to trigger it.
Also create a CallReceivedEventArgs, this will be used to transfer event data:
public class CallReceivedEventArgs : EventArgs
{
public string CallerIDnum {get; set;}
public string CallerIDname {get; set;}
public string ContactID {get; set;}
}
Now, in your api/lib class raise this event whenever a call is received:
// a call received, replace dummy values with actual values
OnCallReceived(new CallReceivedEventArgs() { CallerIDnum="5554443322", CallerIDname="SOME_NAME", ContactID="SOME_CONTACT" });
Finally in your GUI form, register to said event and process accordingly.
// inside your main form class
private CallerAPI callerApi = new CallerAPI();
// somewhere inside you main form class, register to event
// from your use case, I would place it inside Main Form's constructor
callerApi.CallReceived += callerApi_Callreceived;
// receive event
void callerApi_Callreceived(object sender, CallReceivedEventArgs e)
{
var callerIDnum = e.CallerIDnum;
// etc.
// show callerId form with details
// you need to change frmCallerID's constructor accordingly
CallerIDfrm = new frmCallerID(e.CallerIDnum, CallerIDname, ContantID);
// to be able to track opened popups, you can store them inside a list
// private List<Form> openPopupList = new List<Form>();
//
// alternatively, you can assign CallerIDnum to form's name property
// and store these inside a List<string> instead of List<Form>
openPopupList.add(CallerIDfrm);
CallerIDfrm.Show();
}
Don't forget to unregister from event.
callerApi.CallReceived -= callerApi_Callreceived;
To wrap it up:
I can't spawn another popup box I have to use the one I created at runtime
You can create and show multiple frmCallerID, independent from each other.
The forms flash briefly on load - now that I have 3 forms its pretty obvious what I'm doing.
Since new approach creates CallerID forms based on events, you won't see these form flashing. It'll open whenever a CallReceived event is received.
I have to use a variable bool to hold if the popup is open or not.
A better approach would be: Register to forms FormClosed event, and remove from openPopupList accordingly.
frmCallerID.FormClosed += frmCallerID_FormClosed;
void frmCallerID_FormClosed(object sender, EventArgs e)
{
// remove form from openPopupList
frmCallerID closedPopup = (frmCallerID) sender;
openPopupList.remove(closedPopup);
}

Creating custom Label in C# and passing data in events

I had been playing around with an idea for a game, and implementation was going fairly well, but I have hit a stumbling block.
Basically, I have a form, which will show talent trees. I am just going to use labels to display the relevant details, and I want to create them programmatically. The display part is working fine, the part I am having trouble with is adding an event handler to the labels.
I want to be able to pass data during the event handling, so that I can identify which specific label was clicked, but I am hitting a brick wall. So when a particular label is clicked, the name of its associated skill (just passing a string) will be sent to the event handler. Any help would be appreciated. Here is the relevant code that I have:
public void DisplayTree()
{
int i=0;
startPoint.X = 40;
startPoint.Y = 125;
foreach(SkillNode s in tree.tier1)
{
for (i=0; i < s.labels.Count;i++ )
{
//Displays a label for each available rank for a skill
s.labels.ElementAt(i).Text = (i+1).ToString()+"/"+s.maxRank.ToString();
s.labels.ElementAt(i).Location = startPoint;
startPoint.Y += s.labels.ElementAt(i).Height + 2;
s.labels.ElementAt(i).Name = "lbl"+s.name+i.ToString();
//Only enable it to be clicked if the user is at the correct rank
if (s.rank == i)
{
s.labels.ElementAt(i).Enabled = true;
}
else
{
s.labels.ElementAt(i).Enabled = false;
}
//Add Event here
//I want to pass the name of the skill with the event
this.Controls.Add(s.labels.ElementAt(i));
}
startPoint.X += s.title.Width + 5;
startPoint.Y = 125;
}
}
public void LabelClick()
{
//Code here to pick out the name of the label
}
Try this:
public void LabelClick()
{
Console.WriteLine(((Control)sender).Name);
}
When you create an event and want to follow the official C# styleguide, you follow the following pattern:
public delegate void {YourName}EventHandler(object sender, {YourName}EventArgs args);
public event {YourName}EventHandler EventName;
Every information about what happened in the event or can be manipulated by the subscriber is stored in a class that inherits EventArgs. The delegate also contains a reference to the sender, which is the object that fires the event.
When you fire an event you do the following, regularly in a protected method that has the same name as the Event with an "On" as prefix:
EventName?.Invoke(this, new {YourName}EventArgs() { Initialize Stuff });
As you can see, you can work with the sender and identify the object. In your case you could also change object sender to UIElement sender (or similar) to make it easier to identify stuff without a cast.

Emulating a console with a WPF window

Firstly, a disclaimer, what you're about to witness is my first bit of coding for almost 20 years. I'm new to C# and WPF, trying to get my head WPF is more than a challenge.
For the past month I've been working on a pet project console application, which has been performing well. I'm now trying to take it another step further by adding a modern GUI to the project.
I'd like to emulate a console (just the basic outpu functionality) by using a WPF textblock wrapped inside a scroller in a WPF window. You can see the original console application in action here to get a better idea of the kind of console output I'm trying to emulate. But I'm having a major problem with basic function calls, and I assume it's because I don't fully understand how WPF/C# work under the hood.
The Application starts in code via Main() like so:
class Program
{
public static ConsoleWindow MainConsole = new ConsoleWindow();
[STAThread]
static void Main(string[] args)
{
Application MyApplication = new Application();
MyApplication.Run(MainConsole);
// The following code does not work, it produces no output in the Textblock
MainConsole.WriteLine("Crystal Console");
MainConsole.WriteLine("Version: " + Properties.Settings.Default.BuildVersion);
MainConsole.WriteLine("Current Time: " + DateTime.Now);
MainConsole.WriteLine("Last Login: " + Properties.Settings.Default.dateLastLogin);
}
}
The problem is that the methods called don't seem to have any affect on the content of the textblock.
Although I'm about to give a lot of information just in case it's needed, the question itself is quite simple: Why does the Textblock update fine when taking content from a textbox control on the same window, but doesn't show any updates when the same method is called in Main() ?
For testing purposes the window has a few Textboxes that call the .WriteLine method inside the window, and THAT works, so I know there isn't a problem with the .WriteLine code, which you can see here:
public void WriteLine(string Message = null, string Sender = null)
{
_Console.AddElement(new ConsoleElement(Sender, Message + "\n"));
_Console.DisplayContent(ConsoleTextBlock);
ConsoleScroller.ScrollToEnd();
}
Here is the code for the console itself in case it's needed, the class "ConsoleElement" is essentially just a object that contains the messages to be displayed in the Textblock as well as the formatting for each one.
class ConsoleStream
{
IList<ConsoleElement> ConsoleElements = new List<ConsoleElement>();
public void AddElement(ConsoleElement NewElement)
{
if (NewElement.Sender == null) // Sender is System not user.
{
NewElement.Content = " " + NewElement.Content;
NewElement.Font = new FontFamily("Arial");
NewElement.FontSize = 12;
}
ConsoleElements.Add(NewElement);
}
public void ClearElements()
{
ConsoleElements.Clear();
}
public void DisplayContent(TextBlock sender)
{
sender.Text = null;
foreach (ConsoleElement Message in ConsoleElements)
{
//If message is a status update, i.e. has no sender, format it as a system message.
if (Message.Sender != null)
{
sender.Inlines.Add(new Run(Message.Sender + ": ") { Foreground = Message.SenderColour, FontFamily = Message.Font, FontSize = Message.FontSize });
}
//if message has a sender it's either the user or the AI. Format it as a user message.
if (Message.Sender != null) sender.Inlines.Add(new Run(Message.Content) { Foreground = Message.ContentColour, FontFamily = Message.Font, FontSize = Message.FontSize });
else sender.Inlines.Add(new Run(Message.Content) { Foreground = Message.SystemColour, FontFamily = Message.Font, FontSize = Message.FontSize });
}
}
}
MyApplication.Run(MainConsole); takes control of the thread, the code after it doesn't execute until after you close the window.
Move the code to the load (or init) method of your ConsoleWindow

Method executing several times even though it is called once

I am doing a project which includes dynamic controls creation and removal from the WinForm,
So I decided to that part on a small test project.
Test project has two files Form1.cs and NewControls.cs. This program creates additional buttons whenever user clicks an Add button already on the form.And removes the newly created button when it is clicked (self removal button). Also after removal of button other button's Name, Text and their position are changed according to a local variable (controlIndex).
Form1.cs
public partial class Form1 : Form
{
static List<NewControl> newControlsList = new List<NewControl>();
public Form1()
{
InitializeComponent();
}
private void Add_Click(object sender, EventArgs e)
{
newControlsList.Add(new NewControl(newControlsList.Count));
}
public static void RemoveButton(object sender, EventArgs e)
{
NewControl tempNewControl = (NewControl)(sender as Button).Tag;
tempNewControl.RemoveControl();
newControlsList.Remove(tempNewControl);
MessageBox.Show("Removed!");
foreach (NewControl tempcontrol in newControlsList)
{
tempcontrol.controlIndex = newControlsList.IndexOf(tempcontrol);
tempcontrol.PlaceControl();
}
}
}
NewControl.cs
class NewControl
{
public int controlIndex = 0;
Button newButton = new Button();
public NewControl(int index)
{
controlIndex = index;
PlaceControl();
}
public void RemoveControl()
{
newButton.Dispose();
Form1.ActiveForm.Controls.Remove(newButton);
}
public void PlaceControl()
{
newButton.Tag = this;
newButton.Name = "btn" + controlIndex.ToString("D2");
newButton.Text = "btn" + controlIndex.ToString("D2");
newButton.Size = new Size(100, 20);
newButton.Left = controlIndex * 100;
Form1.ActiveForm.Controls.Add(newButton);
newButton.Click += new EventHandler(Form1.RemoveButton);
}
}
Program works nearly as expected. Problem is the MessageBox which I used in form1.cs in RemoveButton() fires many time (as opposed to just one time), which implies whole method being executed several times. Actually I pasted that MessageBox for debugging (sort of).
Since I cannot debug the application as when "Form1.ActiveForm.Controls.Add(newButton);" statement is executed, debugger Throws NullReferenceException, as there is not an active form while debugging.
I know that's like a bonus question but I thought to just put it there. I am a beginner and can't see the way through both the problems. 1st problem is really important for my original project as it will cause problem when many controls are added.
I think it is because you call PlaceControl from Form1.cs AND in the constructor of the NewControl class, Because you say newButton.Click += new EventHandler(Form1.RemoveButton);.
You are adding EventHandlers, so there can be more of them.
So when you call placeControl multiple times, you've got multiple event handlers, i think.
Probably the EventHandler hasn't been removed by RemoveButton. (I've been working in java most recently so my terms might be a little off for C#.) Suggestion: set control visibility to true when you want it and false otherwise rather than adding and removing.
Everytime a button is removed you go over your existing list of controls, and you call "PlaceControl", which attaches yet another handler.
foreach (NewControl tempcontrol in newControlsList)
{
tempcontrol.controlIndex = newControlsList.IndexOf(tempcontrol);
tempcontrol.PlaceControl();
}
Remove the above code block from RemoveButton, and you will see that your dynamically added buttons will each only trigger the event once.
In your RemoveButton event you loop on each button and call again PlaceControl.
The only reason is to reposition the remainder controls.
I think it's better a call to a separate method that do only this work.
public void RepositionControl()
{
newButton.Left = controlIndex * 100;
}
this will prevent to mess with event handlers added more than one time

Categories