I read about Control.ParentChanged Event on MSDN
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.parentchanged(VS.71).aspx
But I don't understand the sample code: there's no ParentChanged at all appearing in source code ?
private void currencyTextBox_TextChanged(object sender, EventArgs e)
{
try
{
// Convert the text to a Double and determine if it is a negative number.
if(double.Parse(currencyTextBox.Text) < 0)
{
// If the number is negative, display it in Red.
currencyTextBox.ForeColor = Color.Red;
}
else
{
// If the number is not negative, display it in Black.
currencyTextBox.ForeColor = Color.Black;
}
}
catch
{
// If there is an error, display the text using the system colors.
currencyTextBox.ForeColor = SystemColors.ControlText;
}
}
So I don't understand what Control.ParentChanged Event is or does.
Hehe, they just couldn't come up with a good example. And punted by showing a generic FooChanged event handler instead. Yeah, useless.
It is quite unusual to implement a ParentChanged event handler yourself. It's a big deal in the Winforms internals, properties like BackColor, ForeColor, Font are 'ambient' properties. If they are not overridden from the default then they'll get the value of the Parent. Which of course means that it is really important to notice that the parent changed. The winforms code already takes care of it, you very rarely have to worry about it. Unless you create your own ambient property of course.
There would be another piece of code elsewhere that registers this as an event handler:
currencyTextBox.ParentChanged += new EventHandler(currencyTextBox_TextChanged);
However, I agree - the method name is misleading.
This event handler will fire when you change the parent control of this control to a different parent control.
You may want to read up on raising and consuming events.
Related
I have a custom WPF control that inherits from RichTextBox. I'd like to be able to modify the FlowDocument of the richtextbox whenever the text is changed. For demonstration, let's say that we have this:
<MyCustomRichTextbox>
<FlowDocument>
<Paragraph>This is the first paragraph</Paragraph>
<Paragraph>And this is the second</Paragraph>
</FlowDocument>
</MyCustomRichTextbox>
and whenever the text is changed (e.g. someone types in the control), the entire containing paragraph is colored red.
It seems to me that two things need to happen in order to achieve this:
I need to somehow get the block that contains the changed text out of MyCustomRichTextbox.Document.Blocks
I need to modify that block
Unfortunately, the OnTextChanged method doesn't provide a way to get the Block that changed. I was able to use LINQ and the TextChangedEventArgs.Offset to get the block, but I'm concerned that this approach will yield unacceptable slowdowns with larger documents (since it must enumerate each block every time a character is typed). Is there a better way to get the containing paragraph?
I know I could cache a reference to the "Last modified block" and check if it's still the one being modified, but that wouldn't really help in a random access scenario.
If I understand your problem correctly, you want to highlight the containing Paragraph of the current selection (the current position of the caret). So it's obviously that you have to get the containing Paragraph each time the Selection changes. Then you can just change the Foreground to Brushes.Red. Fortunately that the containing Paragraph seems to be referenced by the TextPointer and the process of finding it is nearly immediate. (a TextPointer has a property called Paragraph). Here is the detailed code:
Paragraph p = null;
//Suppose rtb is the name of your RichtTextBox
private void UpdateContainingBlockState() {
if (p != rtb.Selection.Start.Paragraph){
if (p != null) p.Foreground = Brushes.Black;
p = rtb.Selection.Start.Paragraph;
if (p != null) p.Foreground = Brushes.Red;
}
}
//The SelectionChanged event handler for your RichTextBox
private void selectionChangedHandler(object sender, RoutedEventArgs e){
UpdateContainingBlockState();
}
The frequency of changing the Selection is fairly high (each time you press almost keys which can cause the selection changing). So if your document is large and you realize some poor performance while typing, it's time to switch to the next more complex code. You can also try using Threading approach (or using Task) to put the UpdateContainingBlockState() call in another thread but be careful about cross-thread access. Here I use a different approach, the idea is call the UpdateContainingBlockState() at the right time, that is when the actual selection change can jump between paragraphs. While typing normal printable characters, the selection will be always in the current paragraph (so we don't need to call UpdateContainingBlockState()) unless when you type the Enter key. Generally we will call the method when user typing a control key (arrow keys, home, end, Enter, ...). We should also call the method if the RichTextBox gets focused and if user clicks mouse on the RichTextBox. You can see that almost the typed characters won't trigger calling the method so it will improve the performance much more than the code above (of course it may be realizable only when the document is large). Here is the detailed code:
//should add this using at the beginning
using System.Runtime.InteropServices;
[DllImport("user32")]
private static extern int MapVirtualKey(int ucode, int mapType);
//The KeyUp event handler for your RichTextBox
private void keyUp_Handler(object sender, KeyEventArgs e){
if (char.IsControl((char) MapVirtualKey(KeyInterop.VirtualKeyFromKey(e.Key),0x2)))
UpdateContainingBlockState();
}
//The PreviewMouseUp event handler for your RichTextBox
private void previewMouseUp_Handler(object sender, MouseButtonEventArgs e){
//UpdateContainingBlockState();
//Calling UpdateContainingBlockState() directly will cause a small issue
//So we use this instead
Task.Run(() => Dispatcher.Invoke( () => UpdateContainingBlockState()));
}
//The GotKeyboardFocus event handler for your RichTextBox
private void gotKeyboardFocus_Handler(object sender,
KeyboardFocusChangedEventArgs e){
UpdateContainingBlockState();
}
I have a question regarding updating Controls in .Net in such a way so that if the user updates one field, the other field will be updated with some data automatically and vice versa. I am using two NumericUpDown controls which convert some data.
The problem I am having is that, I am using the ValueChanged event. Because of this, sometimes these controls get stuck in a loop where one controls updates the other and the other tries to update the first one. The result is somewhat random.
So, what would be the best approach to handle this situation? In short, I only want to update the other control, if the first one was modified by a user himself.
Any help would be greatly appreciated.
Just use a boolean guard in the class to check whether you are inside an update method.
While you are updating, all future events fired from the NUDs will be ignored.
private boolean updating = false; // Class level variable
void event_handler(...) // The function hooked up to the ValueChanged event
{
if( !updating )
{
updating = true;
// Do your calculations and update the NUDs
updating = false;
}
}
I would recommend that you use data binding and bind to an object which serves as your model. Your model then is where the logic goes that alters the other value based on changing of a property. The model also raises IPropertyChanged / IPropertyChanging events, which the UI will pick up on. Not only will this prevent the issue you describe, it also keeps this business logic outside of your UI layer should you move to something else (say from WinForms to WPF, or Asp.Net MVC).
If method Foo handles the event of one control and method Bar handles the event for the other, then Foo should change the values of Bar's control and vice-versa. But you should use a control variable somewhere (say, a reference to the control that fired the event is a good idea). So that if Foo is called:
Foo updates the value of Bar's control;
Bar's control fires its event, and Bar is called;
Bar checks the reference for the control that shot first, sees that it's not its control, and does nothing.
Same logic applies to Bar.
that way you don't get an infinite loop.
In code, it'd, look like this:
nud1.ValueChanged += new Eventhandler(Foo);
nud2.ValueChanged += new Eventhandler(Bar);
NumericUpDown shooter = null;
private void Foo (object sender, EventArgs e)
{
if (this.shooter == null)
{
this.shooter = nud1;
nud2.Value = nud1.Value;
}
else this.shooter = null;
}
private void Bar (object sender, EventArgs e)
{
if (this.shooter == null)
{
this.shooter = nud2;
nud1.Value = nud2.Value;
}
else this.shooter = null;
}
Of course, this is a crude example (for example, it assumes the value of both controls is always changing. Adapt to your situation.
I like Andy's response about going with an MVC pattern, but if that's too radical of a change for this specific situation, you should set the values only if the current value is different than the value being assigned. That would prevent the ValueChanged event from firing again and stop the infinite loop the first time recursion happens.
// Inside your value changed handler for Control2,
// instead of directly setting the value of Control1, do this:
if(Control1.Value != valueBeingSet)
{
Control1.Value = valueBeingSet;
}
I decided to try a third-party's implementation of the DataGridView, specifically for the purpose of taking advantage of hierarchical grids, i.e. "Grid within a grid" functionality. While this is working out fairly well, I noticed some of the events and properties I depend on with normal DataGridViews don't exist in this implementation, and had to discover different ways to accomplish the same goals.
One such goal was that, when I use a CheckBoxColumn and the user "checks" or "un-checks" the box, I need to push that change to the grid (and subsequently the database) when it happens, otherwise the user would have to hit enter or leave the cell focus to actually commit the change.... Kind of wonky. To remedy this, I took advantage of the 'CurrentCellDirtyStateChanged' event, and manually committed the check or un-check to the grid, which in turn fired my 'CellValueChanged' event:
void dataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if ((dataGridView.IsCurrentCellDirty) & (dataGridView.CurrentCell.ColumnIndex == 0))
{
dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
However, this event doesn't exist with the third-party grid, and neither does the CommitEdit method. In order to achieve the same functionality, I ended up having to do this:
void GridView_ValueChanged(object sender, EventArgs e)
{
if(sender.GetType().ToString().Contains("RadCheckBoxEditor"))
{
RadCheckBoxEditor rad_chb = (RadCheckBoxEditor)sender;
GridCheckBoxCellElement checked_a_box = (GridCheckBoxCellElement)rad_chb.OwnerElement;
checked_a_box.Value = rad_chb.Value;
GridViewCellEventArgs new_args = new GridViewCellEventArgs(checked_a_box.RowInfo, checked_a_box.ColumnInfo, checked_a_box.Editor);
GridView_CellValueChanged(checked_a_box, new_args);
}
else
{
return;
}
}
I essentially caught the dirty value, assigned it to the value of a type recognizable by 'CellValueChanged' handler, defined my own event args, and explicitly called the handle. Are there any potential smells to something like this? It just feels wrong. I noticed others suggested making an independent function to access shared code, yet, those examples didn't seem to depend on a specific event pushing an event.
Okay, I was able to create a simple Windows Forms project that reproduces some strange behavior I found. In the designer, make a form with a ListBox (named lbx) anchored Top, Left, Right, and Bottom, and a button (button1). Now, the Form's code is here:
using System;
using System.Windows.Forms;
namespace ListBoxKaboom
{
public partial class Form1 : Form
{
private bool _initFinished = false;
public Form1()
{
InitializeComponent();
this._initFinished = true;
this.Height += 100;
this.Height -= 50;
this.Height += 50;
}
private void lbx_SelectedIndexChanged(object sender, EventArgs e)
{
this.button1.Enabled = (this.lbx.SelectedItem != null);
}
protected override void OnLayout(LayoutEventArgs e)
{
if (_initFinished)
{
int lines = (this.lbx.Height - 4) / this.lbx.ItemHeight;
this.SuspendLayout();
while (lines < this.lbx.Items.Count)
{
this.lbx.Items.RemoveAt(this.lbx.Items.Count - 1);
}
while (lines > this.lbx.Items.Count)
{
this.lbx.Items.Add("Item " + (this.lbx.Items.Count + 1).ToString());
}
this.ResumeLayout();
}
base.OnLayout(e);
}
}
}
PLEASE NOTE THE FOLLOWING INSTRUCTIONS:
Run this, click any of the items in the list box, and use the arrow keys to move down far enough to cause the list box to scroll. Kaboom.
Exception (sometimes NullReferenceException and sometimes IndexOutOfBoundsException). Any ideas why? Also, I would think that the items would be in order, but they're not. Is this just a goofy corner case that didn't get handled properly by Windows Forms, or am I doing something wrong?
Stack trace:
at System.Windows.Forms.ListBox.NativeUpdateSelection()
at System.Windows.Forms.ListBox.SelectedObjectCollection.EnsureUpToDate()
at System.Windows.Forms.ListBox.SelectedObjectCollection.get_InnerArray()
at System.Windows.Forms.ListBox.SelectedObjectCollection.get_Item(Int32 index)
at System.Windows.Forms.ListBox.get_SelectedItem()
I Copy/Pasted it to an empty Form and get a StackOverflow exception. Looking at it, with manipulation of the Items inside a Layout event I would say you deserve little better.
I realize this may be a simplification of something else, but there simply are limits to what you can do in a EDS.
My best guess: The ResumeLayout triggers a recursive layout operation. You could try to stem it with a sibling to _initFinished but I would suggest rethinking tour design here.
I copy/pasted wrong, my bad (used layout event).
Second try:
based on the two while-loops I would expect the Item strings to be in order, and no vertical scrollbar. It is clear that the listbox is confused, showing vertical scroll range and with the items out-of-order. So some 'error' is already present in the internals of the Listbox, waiting for a Scroll. I can also reproduce it with the mouse.
A workaround: You should be able to get the desired effect using the Resize event.
Attempt to an explanation: The (unmanaged part of the) Listbox gets confused by (multiple) Add/RemoveAt operations with suspended Layout. The last items are drawn at the wrong place, and the Listbox can't compute pixel-to-item.
You should not manipulate any GUI elements in the
constructor, e.g. this.Height += 100; in your example.
Strange things can happen. I have been bitten by this
several times in legacy code.
Wait until form load time - handle the base.Load event and do the
height manipulation there.
From "When does Form.Load event get raised?":
Q: "... I need to find out basically
what the difference is between
putting code in Load event's handler,
versus putting code in the Form's
constructor after the
InitializeComponents() line ..."
A: "The Load event is fired once the
control/form has been fully
initialized and has a window handle
created. Therefore once this event
has fired it is a fully usable user
interface control. Remember that
inside the constructor the actual
window handle for the control/form has
not yet been created, you are only
creating C# objects here and inside
the InitializeComponent call."
I am trying to load a preferences window for my application and I would like the apply button to initially be disabled, then when a preference is updated, the apply button gets enabled again. I have some controls data bound to a preferences object and what happens is that after the window loads, the combobox events get triggered. Is there any event that is guaranteed to happen dead last after everything is stable?
Here is what my code looks like (the apply button is always enabled after the window loads):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_preferencesData = new PreferencesDataContext();
LayoutRoot.DataContext = _preferencesData;
ButtonApply.IsEnabled = false;
}
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ButtonApply.IsEnabled = true;
}
Is it also interesting to note that this only happens with textboxes and comboboxes, not checkboxes or radiobuttons.
Best solution for simple need
Joseph's answer is the best solution by far for your simple need: Just use data binding and let the data model handle it.
Answer to question as posed
There are more complex scenarios when you really do need control after absolutely everything has finished loading and all events have fired. There is no single event that occurs "dead last", but it is easy to effectively roll your own using the Dispatcher queue.
This is how to do it:
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
var x = ComputeSomething(1, 2, 3);
DoSomething(x, "Test");
}));
Everything inside the { } will be executed when WPF finishes everything at a higher priority than ContextIdle, which includes all event handlers, loaded events, input events, rendering, etc.
Sequence of events when a Window is created and shown
As requested, here is the sequence of major events in WPF when a window is created and shown:
Constructors and getters/setters are called as objects are created, including PropertyChangedCallback, ValidationCallback, etc on the objects being updated and any objects that inherit from them
As each element gets added to a visual or logical tree its Intialized event is fired, which causes Styles and Triggers to be found applied in addition to any element-specific initialization you may define [note: Initialized event not fired for leaves in a logical tree if there is no PresentationSource (eg Window) at its root]
The window and all non-collapsed Visuals on it are Measured, which causes an ApplyTemplate at each Control, which causes additional object tree construction including more constructors and getters/setters
The window and all non-collapsed Visuals on it are Arranged
The window and its descendants (both logical and visual) receive a Loaded event
Any data bindings that failed when they were first set are retried
The window and its descendants are given an opportunity to render their content visually
Steps 1-2 are done when the Window is created, whether or not it is shown. The other steps generally don't happen until a Window is shown, but they can happen earlier if triggered manually.
The Window.ContentRendered event fulfilled my requirements.
I just did kind of the same thing behaviorly in a systray WPF app.
However, I didn't do it using event handling. I simply bound the Enabled property of my button to a property in my ViewModel, and had the property updated whenever I needed the behavior.
You can use ManagedSpy to figure this out on your own.
http://msdn.microsoft.com/en-us/magazine/cc163617.aspx
Setting the DataContext will likely fire the SelectionChanged event, and you can't rely on when exactly it's fired. Some logic checking on what exactly is selected would be more reliable:
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (myComboBox.SelectedItem == null)
{
buttonApply.IsEnabled = false;
}
else
{
buttonApply.IsEnabled = true;
}
}
The reason it's happening afterwards with your code as-is is because the event gets queued on the thread for the UI, so it's up to Windows if it will execute the next line of code in Load, or to handle the other events on the queue.
Not to throw a whole lot of stuff at you that you may or may not be familiar with, but if this is a relatively new codebase, you may want to consider using the MVVM pattern and use Commands instead of the archaic (emphasis mine) eventing model.
Order of Events in Windows Forms
Control.HandleCreated
Control.BindingContextChanged
Form.Load
Control.VisibleChanged
Form.Activated
Form.Shown