Learning WPF with MacDonald's "Pro WPF 4.5 in C#," focusing on Ch5, Events.
How would I write a generic event handler that works with both Labels and TextBoxes to process the MouseDown event initializing a drag & drop procedure?
Here is my TextBox handler:
private void tBSource_MouseDown(object sender, EventArgs e) {
TextBox tBox = (TextBox)sender;
DragDrop.DoDragDrop(tBox, tBox.Text, DragDropEffects.Copy);
}
And my Label handler:
private void lblSource_MouseDown(object sender, EventArgs e) {
Label lbl = (Label)sender;
DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Copy);
}
As you can see, I'm using the Content property and the Text property, depending on which object starts the event. If I try to use the same property for both senders, I get build errors (regardless of which I use). If I can avoid duplication, I would be very happy. Should I chunk a conditional out into another function and call that in the handler to determine what property should be used?
you could do something like this:
private void Generic_MouseDown(object sender, EventArgs e)
{
object contentDrop = string.Empty;
//Label inherits from ContentControl so it doesn't require more work to make work for all controls inheriting from ContentControl
if (sender is ContentControl contentControl)
{
//If you don't want to filter other content than string, you can remove this check you make contentDrop an object
if (contentControl.Content is string)
{
contentDrop = contentControl.Content.ToString();
}
else
{
//Content is not a string (there is probably another control inside)
}
}
else if (sender is TextBox textBox)
{
contentDrop = textBox.Text;
}
else
{
throw new NotImplementedException("The only supported controls for this event are ContentControl or TextBox");
}
DragDrop.DoDragDrop((DependencyObject)sender, contentDrop, DragDropEffects.Copy);
}
Let me know if you have any question
Related
I have got how to SelectAll text when clicked on a TextBox; I want to do the same for an editable combobox - din find anything. My code for TextBox is
private void OnPreviewMouseDown(Object sender, MouseButtonEventArgs e)
{
txtBox.SelectAll();
txtBox.Focus();
e.Handled = true;
}
How can the same can be done for the Editable Combobox ?
Update
Code for Combox that gives me the output that I want:
private void cboMouseDown(object sender, MouseButtonEventArgs e)
{
var textBox = (cbo.Template.FindName("PART_EditableTextBox", cbo) as TextBox);
if (textBox != null)
{
textBox.SelectAll();
cbo.Focus();
e.Handled = true;
}
}
But now the dropdown of the combobox doesn't work, any suggestion ?
Update-2: Instead of PreviewMouseDown - I have tried PreviewMouseUp and now the dropdown does appear; but when once clicked on the box and then tried to open the dropdown - the window becomes frozen.
However, I have made a work around that I have put in my answer bellow. I would really appreciate your comments though if it is a right and safe solution I can go with.
Use GotFocus event and select text like this
var comboTextBoxChild = comboBox.FindChild(typeof(TextBox), "PART_EditableTextBox") as TextBox;
comboTextBoxChild .SelectAll();
Here combobox is your Editable Combobox name
A possible solution I have got and its working for me - need some suggestion though if it is ok or not; I am using PreviewMouseUp event of the ComboBox:
private void cboMouseUp(object sender, MouseButtonEventArgs e)
{
var textBox = (cbo.Template.FindName("PART_EditableTextBox", cbo) as TextBox);
if (textBox != null && !cbo.IsDropDownOpen)
{
Application.Current.Dispatcher.BeginInvoke(new Action(()=>{
textBox.SelectAll();
textBox.Focus();
//e.Handled = true;
}));
}
I am a little late to party, but I had same problem recently and after testing several solutions I came up with my own (I created custom control for this purpose):
public class ComboBoxAutoSelect : ComboBox
{
private TextBoxBase textBox;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
textBox = GetTemplateChild("PART_EditableTextBox") as TextBoxBase;
}
protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
// if event is called from ComboBox itself and not from any of underlying controls
// and if textBox is defined in control template
if (e.OriginalSource == e.Source && textBox != null)
{
textBox.Focus();
textBox.SelectAll();
e.Handled = true;
}
else
{
base.OnPreviewGotKeyboardFocus(e);
}
}
}
you can do the same with events but you will need to search for "PART_EditableTextBox" every time and here we do it only once per template change
I have a windows form consisting of a series of textboxes and a button.
The user needs to input some data into the textboxes and then the code uses these inputs to do some calculation.
The user then clicks the button and a chart is generated showing the results of the calculations.
The chart is done using R which is connected to C# via R.Net.
The question is: how can I make the chart to update dynamically as soon as the user changes some input in one of the textboxes (so without first clicking the button that generates the graph)?
I thought that I would need some loop that constantly checks if any of the textboxes has been changed but I cannot make this work:
foreach (Control subctrl in this.Controls)
{
if (subctrl is TextBox)
{
((TextBox)subctrl).TextChanged += new EventHandler();
}
}
TextChanged should trigger the buttonClick event so that the reated code that generates the graph is executed.
What is a good approach for this problem? Thanks.
<<<< EDIT >>>>>
Here is my code for the form:
public partial class myInputForm : Form
{
public myInputForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// call methods to run calculations using new inputs from textboxes
plotGraph(); // plot the new results
}
}
I would like to keep the calculation methods and the plot function plotGraph() inside the button1_Click event.
Trying to adopt Refus L's suggestion, I am adding the following to the partial class myInputForm above:
private void TextBox_TextChanged(object sender, EventArgs e)
{
TextBox textbox = sender as TextBox;
if (IsValidText(textBox.Text))
{
textbox.TextChanged += new System.EventHandler(this.button1_Click);
}
else
{
DisplayBadTextWarning(textBox.Text);
}
}
private void myInputForm_Load(object sender, EventArgs e)
{
foreach (Control control in this.Controls)
{
var textBox = control as TextBox;
if (textBox != null)
{
textBox.TextChanged += TextBox_TextChanged;
}
}
}
But this still doesn't work. If I insert the following:
this.myFirstBox.textChanged += new System.EventHandler(this.button1_Click);
directly in the code that is automatically generated by the form designer it works and a change in the textbox myFirstBox triggers the button click and thus the plot.
But I would need to write a line for each textbox cause the foreach doesn't work there.
Can you please explain how to set this up to work in my form? Thanks.
You can just specify an existing method that you want to handle the event. Ideally you'd have a separate method that updates the chart, which can be called from anywhere. Then you can call it from your TextChanged or Button_Click event, but these two control events are not tied together (in TextChanged you may want to do some validation on the text first). Also, if you want to update the chart from some other place, you have an independent method you can call that will do it.
For example, if you have this method to update the chart:
private void UpdateChart()
{
// Do something here to update the chart
}
You can call it from your event handlers:
private void button1_Click(object sender, EventArgs e)
{
UpdateChart();
}
private void TextBox_TextChanged(object sender, EventArgs e)
{
// You might have some data validation on the TextBox.Text here,
// which you wouldn't need in the Button_Click event
TextBox textbox = sender as TextBox;
if (IsValidText(textBox.Text))
{
// Now update the chart
UpdateChart();
}
else
{
DisplayBadTextWarning(textBox.Text);
}
}
Then you can hook up all your TextBox.TextChanged events to the custom handler above:
private void Form1_Load(object sender, EventArgs e)
{
// Dynamically hook up all the TextBox TextChanged events to your custom method
foreach (Control control in this.Controls)
{
var textBox = control as TextBox;
if (textBox != null)
{
textBox.TextChanged += TextBox_TextChanged;
}
}
}
This is probably an easy one for some of you.
I have a TextBox and a ListBox. ListBox provides options for the TextBox and copies selected item's text to TextBox on DoubleClick event. ListBox becomes visible only when TextBox fires Enter event. I do not want to discuss my reasons for selecting this control combination.
I want ListBox to disappear when any other control within the Form gets focus. So I capture Leave event of TextBox and call ListBox.Visible = fale The problem is that TextBox will also loose focus when I click on ListBox to select provided option thus preventing me from selecting that option.
What event combination should I use to preserve ListBox to select option but hide it whenever other controls get focus?
In the Leave method, you can check to see if the ListBox is the focused control or not before changing its Visibility:
private void myTextBox_Leave(object sender, EventArgs e)
{
if (!myListBox.Focused)
{
myListBox.Visible = false;
}
}
This example will provide you with the desired outcome:
public Form1()
{
InitializeComponent();
textBox1.LostFocus += new EventHandler(textBox1_LostFocus);
textBox1.GotFocus += new EventHandler(textBox1_GotFocus);
}
void textBox1_GotFocus(object sender, EventArgs e)
{
listBox1.Visible = true;
}
void textBox1_LostFocus(object sender, EventArgs e)
{
if(!listBox1.Focused)
listBox1.Visible = false;
}
private void listBox1_MouseDoubleClick(object sender, MouseEventArgs e)
{
textBox1.Text = listBox1.SelectedItem.ToString();
}
private void Form1_Shown(object sender, EventArgs e)
{
//if your textbox as focus when the form shows
//this is the place to switch focus to another control
listBox1.Visible = false;
}
I have made a custom Number Keypad control that I want to place in my winform application. All of the buttons have an OnClick event to send a value to the focused textbox in my form where I have placed my custom control. Like this:
private void btnNum1_Click(object sender, EventArgs e)
{
if (focusedCtrl != null && focusedCtrl is TextBox)
{
focusedCtrl.Focus();
SendKeys.Send("1");
}
}
focusedCtrl is supposed to be set on the MouseDown event of the button like this:
private void btnNum1_MouseDown(object sender, EventArgs e)
{
focusedCtrl = this.ActiveControl;
}
where this.ActiveControl represents the active control on the form.
My problem is that the button always receives the focus before the event detects what the focused control was previously. How can I detect which control had the focus before the button got the focus? Is there another event I should be using? Thanks in advance!
EDIT: Also, I would rather not use the GotFocus event on each textbox in the form to set focusedCtrl since that can be tedious and because I would like to have all the coding of my custom control be in the control itself and not on the form where it is placed. (I will do this, though, if there is no other practical way to do what I am asking)
Your requirement is fairly unwise, you'll want some kind of guarantee that your button isn't going to poke text into inappropriate places. You really do need to have the form co-operate, only it knows what places are appropriate.
But it is not impossible, you can sniff at input events before they are dispatched to the control with the focus. In other words, record which control has the focus before the focusing event is fired. That's possible in Winforms with the IMessageFilter interface.
Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form, replacing your existing buttons.
using System;
using System.Windows.Forms;
class CalculatorButton : Button, IMessageFilter {
public string Digit { get; set; }
protected override void OnClick(EventArgs e) {
var box = lastFocused as TextBoxBase;
if (box != null) {
box.AppendText(this.Digit);
box.SelectionStart = box.Text.Length;
box.Focus();
}
base.OnClick(e);
}
protected override void OnHandleCreated(EventArgs e) {
if (!this.DesignMode) Application.AddMessageFilter(this);
base.OnHandleCreated(e);
}
protected override void OnHandleDestroyed(EventArgs e) {
Application.RemoveMessageFilter(this);
base.OnHandleDestroyed(e);
}
bool IMessageFilter.PreFilterMessage(ref Message m) {
var focused = this.FindForm().ActiveControl;
if (focused != null && focused.GetType() != this.GetType()) lastFocused = focused;
return false;
}
private Control lastFocused;
}
Control focusedCtrl;
//Enter event handler for all your TextBoxes
private void TextBoxesEnter(object sender, EventArgs e){
focusedCtrl = sender as TextBox;
}
//Click event handler for your btnNum1
private void btnNum1_Click(object sender, EventArgs e)
{
if (focusedCtrl != null){
focusedCtrl.Focus();
SendKeys.Send("1");
}
}
you have an event called lostFocus you can use
button1.LostFocus +=new EventHandler(dataGridView1_LostFocus);
and in the event:
Control lastFocused;
void dataGridView1_LostFocus(object sender, EventArgs e)
{
lastFocused = sender as Control;
}
in that way you can always know what is the Control that was focused previously
now, correct me if i'm wrong, but you do it for the SendKeys.Send("1"); to know which textBox need to receive the number. for that you can use GotFocus event and register only the textBoxs to it.
you can also do what windows is doing and use just one textbox like here:
if it's fits your needs
What about using this with the parameter forward = false?
Control.SelectNextControl Method
You'd probably call it on your "custom Number Keypad control".
I'm creating listviews in a flowpanel at run time which later will accept drag and dropped files. the reason being is i want these to act as folders so a user double clicks and gets a window displaying the contents.
i'm having difficulty setting up the events for my listviews as they are added.
how do i create some events (like MouseDoubleClick and DragDrop) dynamically for each added listview? can i create a single function for both of these events and have listview1, listview2, listviewX use it?
i have a button that is adding the listviews, which works fine. please advise, i apologize if this is too conceptual and not exact enough.
private void addNewWOButton_Click(object sender, EventArgs e)
{
ListView newListView = new ListView();
newListView.AllowDrop = true;
flowPanel.Controls.Add(newListView);
}
You would have to have the routine already created in your code:
private void listView_DragDrop(object sender, DragEventArgs e) {
// do stuff
}
private void listView_DragEnter(object sender, DragEventArgs e) {
// do stuff
}
and then in your routine, your wire it up:
private void addNewWOButton_Click(object sender, EventArgs e)
{
ListView newListView = new ListView();
newListView.AllowDrop = true;
newListView.DragDrop += listView_DragDrop;
newListView.DragEnter += listView_DragEnter;
flowPanel.Controls.Add(newListView);
}
You would have to check who the "sender" is if you need to know which ListView control is firing the event.
You can also just use a lambda function for simple things:
newListView.DragEnter += (s, de) => de.Effect = DragDropEffects.Copy;
Just make sure to unwire the events with -= if you also remove the ListViews dynamically.
To answer the other half of your question, you can use a single handler for any event, from any source, that has the handler's signature. In the body of the handler, you just have to check the sender argument to determine which control raised the event.
You need a way to tell one control from a different one of the same class, however. One way to do this is to make sure to set the Name property on each control when you create it; e.g., newListView.Name = "FilesListView".
Then, before you do anything else in your event handler, check the sender.
private void listView_DragDrop(object sender, DragEventArgs e) {
ListView sendingListView = sender as ListView;
if(sendingListView == null) {
// Sender wasn't a ListView. (But bear in mind it could be any class of
// control that you've wired to this handler, so check those classes if
// need be.)
return;
}
switch(sendingListView.Name) {
case "FilesListView":
// do stuff for a dropped file
break;
case "TextListView":
// do stuff for dropped text
break;
.....
}
}