Why does delegate subscriber get called after event has passed?
I'm not sure how to ask this, but here goes. I have a WPF window that has a delegate that responds to a TextChanged event of a TextBox. When I load data into the window, and then subscribe my controller class to that event, the delegate handler method gets called.
The sequence is this. 1. Create Window 2. Load data for that window. 3. Subscribe to the TextChanged event for the window with a TextDidChange method.
In this scenario, my TextDidChange method gets called, even though the "event" occurred in step 2. Is this expected behavior? If not, what could be going on?
EDIT: Here's the relevant code. I haven't posted the event handling from the UserControl, as it's boilerplate (if the delegate != null, call the delegate).
From the Controller constructor:
public ServiceRequestVM(Boolean isDataSourceProd, codExistServiceRequestSearchType requestIdOrMapNo, String aMapNumber, Decimal aRequestId) {
//create the schema and load any necessary data
_sroc = new ServiceRequestOracleController();
_sroc.IsProd = isDataSourceProd;
_isProd = isDataSourceProd;
_isNewRequest = false;
_searchType = requestIdOrMapNo;
createSchema();
if (requestIdOrMapNo == codExistServiceRequestSearchType.MapNumber) {
loadMatchingRequest(aMapNumber);
} else {
loadMatchingRequest(aRequestId);
开发者_开发知识库}
Decimal _reqId = (Decimal)_serviceRequestTable.Rows[0]["REQUESTID"];
loadNotesForRequest(_reqId);
loadTagsForRequest(_reqId);
Decimal _custId = (Decimal)_serviceRequestTable.Rows[0]["CUSTOMERID"];
getNameForCustomerAndSetCustomerIdForRequest(_custId);
//configure the UI
configureUI();
customerListBoxVisibility = Visibility.Hidden;
tagListBoxVisibility = Visibility.Hidden;
//create the view (a UserControl)
_serviceRequestView = new ServiceRequestView();
_serviceRequestView.DataContext = this;
//load customers and tags
loadCustomers();
loadTags();
_shouldListBoxesBeSeen = false;
//subscribe to delegates
subscribeToRequestDelegates();
}
The subscribeToRequestDelegates method
private void subscribeToRequestDelegates() {
_serviceRequestView.addNoteButtonWasClicked += new ServiceRequestView.AddNoteButtonWasClickedHandler(addNote);
_serviceRequestView.addTagButtonWasClicked += new ServiceRequestView.AddTagButtonWasClickedHandler(addTag);
_serviceRequestView.locateButtonWasClicked += new ServiceRequestView.LocateButtonWasClickedHandler(locateMap);
_serviceRequestView.openButtonWasClicked += new ServiceRequestView.OpenButtonWasClickedHandler(openMap);
_serviceRequestView.saveButtonWasClicked += new ServiceRequestView.SaveButtonWasClickedHandler(saveRequest);
_serviceRequestView.noteWasDoubleClicked += new ServiceRequestView.NoteWasDoubleClickedHandler(openSelectedNote);
_serviceRequestView.dateCompletedLostFocus += new ServiceRequestView.DateCompletedLostFocusHandler(dateCompletedDidChange);
_serviceRequestView.titleLostFocus += new ServiceRequestView.TitleLostFocusHandler(titleDidChange);
_serviceRequestView.customerTextChanged += new ServiceRequestView.CustomerTextChangedHandler(customerTextDidChange);
_serviceRequestView.selectedCustomerChanged += new ServiceRequestView.SelectedCustomerChangedHandler(selectedCustomerDidChange);
_serviceRequestView.tagTextChanged += new ServiceRequestView.TagTextChangedHandler(tagTextDidChange);
_serviceRequestView.selectedTagChanged += new ServiceRequestView.SelectedTagChangedHandler(selectedTagDidChange);
_serviceRequestView.tagTextLostFocus += new ServiceRequestView.TagTextLostFocusHandler(tagTextLostFocus);
_serviceRequestView.customerTextLostFocus += new ServiceRequestView.CustomerTextLostFocusHandler(customerTextLostFocus);
_serviceRequestTable.ColumnChanged += new DataColumnChangeEventHandler(serviceRequestTableColumnValueDidChange);
}
I can't tell you for sure what WPF is doing, but under the bonnet in windows, a control's Text is usually updated by sending the window a message. This is placed on the application's message queue and only handled when the UI thread gets around to processing the messages - so if the control is being handled by your UI thread, then you have to finish your initialisation and return control to your main message processing loop (or call DoEvents()) before the control will receive and process the message. i.e. it happens in an asynchronous fashion. (You can also "post" a message to the control, which waits for the control to handle the message before it returns to your code, but it's possible/likely that WPF is not calling it in this way)
The easiest fix is to put a "guard" variable into your class to avoid reacting to "internal" calls, and have your event handler ignore the events when it is set:
bool suppressTextChanged;
void Initialize()
{
suppressTextChanged = true;
control.SetText("abcd");
suppressTextChanged = false;
...
}
void Control_TextChanged(object sender, EventArgs e)
{
if (suppressTextChanged) return;
...
}
It's an ugly but effective solution. It's a shame that windows never provided a way of differentiating user-driven events (e.g. text changed by the user editing it in the control) and internal events (driven by your program updating the control internally) - although for some events you can use the "sender" to determine the source of the event in order to work out how it was triggered.
精彩评论