SelectedIndex is unchanging in tabcontrol - Dispatcher issue - c#

Why, after I set SelectedIndex=0, do I subsequently (not in response) get the event handler invoked with SelectedIndex=4? I traced it down to an invocation of a Dispatcher, but I do not understand why.
Here's the plan: I have a page containting a tab control with assorted tabs. When I click on certain kinds of tabs, I want to switch out of this page to another page. The trouble is, when I return to this page, I'm getting looped back out to the other page again, and I can't figure out why.
Specifically, I set SelectedIndex=0, and then [External Code] changes SelectedIndex to 4, causing the page to be switched out again.
// In JobTreePage.xaml
<TabControl x:Name="mainTab"
ItemsSource="{Binding}"
SelectionChanged="mainTab_SelectionChanged">
// in JobTreePage
private void mainTab_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
e.Handled = true;
if (!ready) return;
var ee = this.TryFindResource("Title");
TabItem x = mainTab.SelectedItem as TabItem;
if (x == null) return;
if (IsJobTab(x)) // index 4 is a job tab
{
if (ready ) // assuming the job selected is 1273.
{
ResetTab();
//mainTab.SelectedItem = AddNew;
mw.moldDataButton_Click(sender, e);
}
ready = true;
}
}
public void ResetTab()
{
ready = false;
Application.Current.Dispatcher.BeginInvoke
((Action)delegate { mainTab.SelectedIndex = 0; }, DispatcherPriority.Send, null);
//mainTab.SelectedIndex = 0;
ready = true;
}
And in the MainWindow I have two buttons:
public void moldDataButton_Click(object sender, RoutedEventArgs e)
{
CurrentPage = this.moldDataPage;
moldDataPage.ActivateTab("Project");
// the problem line. problem arises after I click on the Customers button.
Dispatcher.Invoke(new System.Action(() => { }), DispatcherPriority.ContextIdle, null);
Slow_COM_Operation();
}
private void CustomersButton_Click(object sender, RoutedEventArgs e)
{
this.jobTreePage.ResetTab();
CurrentPage = this.jobTreePage;
}
So tracing the program from CustomersButton_Click(), in ResetTab, SelectedIndex == 0, but after that finishes we jump to mainTab_SelectionChanged and see that SelectedIndex == 4. I have no idea what is forcing SelectedIndex to 4.
Aside: Why do I have this dispatcher line? Because I'm attempting to send an asynchronous request via COM interop to get some other stuff happening without the dialog freezing. I don't know how to achieve the goal, but putting in the line at least gives me a page refresh.

Related

C# communication between forms bug

I'm working on a GUI for an admin interface for management of a student complex. Currently the GUI has a listbox with predefined 6 rules for the students. In the beginning of the code, I add them to a list
private void Form1_Load(object sender, EventArgs e)
{
foreach (string rule in lbRules.Items)
ruleList.Add(rule);
}
Then, the GUI provides the admin with an option to modify the rules. To do so he selects a rule from the listbox and clicks a "Modify" button, which opens another form:
private void BtnModify_Click(object sender, EventArgs e)
{
if (lbRules.SelectedItems.Count > 0)
{
selectedRule = lbRules.SelectedItem.ToString();
selectedIndex = lbRules.SelectedIndex;
selectedRuleNumber = selectedRule.Substring(0, 3);
selectedRule = selectedRule.Substring(6);
var rulesForm = new Rules();
rulesForm.Show();
}
}
On the second form load event I get the rule's text and number:
private void Rules_Load(object sender, EventArgs e)
{
tbRule.Text = Form1.selectedRuleNumber;
tbModifyRule.Text = Form1.selectedRule;
}
The text gets added to a RichTextBox, from where the rule can be edited.
Then the admin clicks a "Save" button, which gets the edited text from the RichTextBox(tbModifyRule) and adds it to a static ruleList in form1, sets a static boolean from form1 to true. Afterwards the second form gets closed:
private void BtnSave_Click(object sender, EventArgs e)
{
saveRule = Form1.selectedRuleNumber + " - " + tbModifyRule.Text;
Form1.ruleList.Insert(Form1.selectedIndex, saveRule);
Form1.ruleList.RemoveAt(Form1.selectedIndex+1);
Form1.formOpen = true;
this.Dispose();
}
At this point we are back to form1, in which we have a timer with timer_tick event. In there we check whether the boolean formOpen is true (which it is set before closing form2). Inside the if statement we clear the listbox and add each rule from the ruleList (previously edited in form2) to the listbox, then sets the formOpen back to false so it doesn't get executed all the time:
if (formOpen)
{
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
formOpen = false;
Now this is really weird, and at this point makes absolutely no sense to me, since I tried debugging it for over an hour, trying different ways, which also led me to mysterious wonders of WHY TF IT WORKS WHENEVER IT WANTS...
So this works randomly, like it would work the first time, the second and third times it won't. Or vice versa. It's all random.
Strangely, I tried adding a breakpoint on the
lbRules.Items.Add(item);
in the foreach loop, so it stops on each item. And I actually saw the changed rule getting added from the ruleList into the listBox, however in the end it was not there.
And weirdly enough, I also tried adding the text from form2 in the listBox in form1, without using a list, but for whatever odd reason, I use the int selectedIndex, which gets the index of the selected item from the BtnModify_Click event to insert the text in that particular index, but this very index gets RANDOMLY set to bloody 0 after form2 closes.
hence, it again works from time to time, because at some tries it doesn't get set to 0 and it works.
if (formOpen)
{
selectedRule = Rules.saveRule;
lbRules.Items.Insert(selectedIndex, selectedRule);
lbRules.Items.RemoveAt(selectedIndex+1);
}
formOpen = false;
I don't assign value to this integer ANYWHERE else in the code.
I really tried digging some sense, but I hit a solid hard rock.
Any help appreciated!
And thanks for the time!
edit1:
as requested - rest of the timer method
private void Timer1_Tick(object sender, EventArgs e)
{
foreach (string text in ws.messages)
message = text;
if (ws.messages.Count > 0)
{
if (message.Contains("comp"))
{
Complaints();
message = String.Empty;
ws.messages.Clear();
}
}
if (formOpen)
{
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
formOpen = false;
}
I would change your code to the following:
if (formOpen)
{
formOpen = false;
lbRules.Items.Clear();
foreach (string item in ruleList)
lbRules.Items.Add(item);
}
The issue with having the formOpen = false; outside the if statement is that there is a chance that once the user clicks the Save button the timer could be about to execute the formOpen = false instruction setting it to false making the code inside the If statement to never be executed.
I truly believe this is not random but just a timing issue due to complicated logic.
If I were you, I'd do a couple things:
Use a separate class for data exchange between forms, avoid using public static (I assume) form members for this.
Instead of a timer, subscribe to the Form.Closed event of RulesForm
This might make code flow a bit more predictable and allow you to find errors more easily.
Better yet, use the following pattern:
class Form1
{
private void BtnModify_Click(object sender, EventArgs e)
{
var ruleData = ..... //get current rule data
var rulesForm = new Rules();
rulesForm.SetData(ruleData); //pass initial state to the form
rulesForm.SaveChanges = this.ApplyRules; //pass a method which will be called on save
rulesForm.Show();
}
private bool ApplyRules(RuleData ruleData)
{
//do whatever you like with the rules here
return true;
}
}
class RuleForm
{
public void SetData(RuleData ruleData)
{
//initialize fields, etc
}
public Func<RuleData, bool> SaveChanges { get; set; }
private void BtnSave_Click(object sender, EventArgs e)
{
var ruleData = .... //get data from form fields
if(this.SaveChanges(ruleData))
this.Close();
}
}
class RuleData
{
//whatever data you need
}

When do items get reloaded in listview when navigating back using mvvm

I'm working on a UWP Windows 10 app. It contains a listview which redirects you to another page when an item is tapped I'm trying to get the last item to be re-selected when I navigate back to the previous page.
My listview's ItemSource is binded to an ObservableCollection and loads my items as expected but when I navigate back, it keeps the item selected but it's not visible.
I've read various articles:
Make ListView.ScrollIntoView Scroll the Item into the Center of the ListView
ListViewBase.ScrollIntoView methods
Windows 10 ScrollIntoView() is not scrolling to the items in the middle of a listview
And quite a few others, and I tried to use the extension from the last article mentioned above which provided various extensions to the Listview and I thought I'd be good to go but to no avail!
While investigating and trying various things out, I thought I'd make a call from
protected async override void OnNavigatedTo(NavigationEventArgs e)
and call the following code:
if (e.NavigationMode == NavigationMode.Back)
{
if (GetViewModel.SelectedChannel != null)
{
await this.lvwChannels.ScrollToIndex
(GetViewModel.SelectedIndex);
}
}
or
if (e.NavigationMode == NavigationMode.Back)
{
if (GetViewModel.SelectedChannel != null)
{
await this.lvwChannels.ScrollToItem
(GetViewModel.SelectedItem);
}
}
but it still not working.
This is when I noticed that my Listview.Items.count was returning 0 and yet the item are reloaded, but I assume they are being reloaded at a later stage so my question is:
but I noticed the following:
When stepping through the extension, the
public async static Task ScrollToIndex(this ListViewBase listViewBase, int index)
my listViewBase.Items.Count is 0?? Why?
When trying to find the ScrollViewer within the ListView, it doesn't find it?
When calling
var selectorItem = listViewBase.ContainerFromIndex(index) as SelectorItem;
selectorItem is null as it doesn't find anything based on the index. The index is the only thing that appears to be set correct i.e. 10, 23, 37
Can someone point me the right direction? I assume it's a timing issue and that my ObservableCollection hasn't been re-binded at this stage. I could re-bind at just before calling the ScrollIntoView, but that's still not going to resolve the fact that my listViewBase is null.
Any ideas?
Thanks.
It does seem to be a timing issue and while I'm not 100% sure it is the correct way to address the problem, it seem to have resolved it by calling the ScrollIntoView within the Loaded event:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
if (GetViewModel.SelectedChannel != null)
lvwChannels.ScrollIntoView(GetViewModel.SelectedChannel);
}
I guess I'm not breaking any MVVM rules either as it is dealing with the UI only at this stage.
When called from the Loaded event, both my ObservableCollection has been reloaded automatically since it is binded and the SelectedChannel is also set correctly and everything appears to be working as expected.
As mentioned in my reply below, by default, the selected items is now highlighted and displayed at the top of the list and I'd prefer if it was centered.
If there is a better way to do this, let me know but for now it will have to do.
Thanks
Where did you add your data to ObservableCollection? I'm not sure how you do this. By me, the following method works fine:
public sealed partial class MainPage : Page
{
ObservableCollection<listText> list = new ObservableCollection<listText>();
static int selectedcount = -1;
public MainPage()
{
this.InitializeComponent();
mylist.ItemsSource = list;
}
class listText
{
public string listtxt { get; set; }
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
list.Clear();
list.Add(new listText { listtxt = "Item1" });
list.Add(new listText { listtxt = "Item2" });
mylist.SelectedIndex = selectedcount;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
switch (mylist.SelectedIndex)
{
case 0:
selectedcount = 0;
this.Frame.Navigate(typeof(Item1));
break;
case 1:
selectedcount = 1;
this.Frame.Navigate(typeof(Item2));
break;
default:
selectedcount = 0;
this.Frame.Navigate(typeof(Item1));
break;
}
}
}
And the NavigateBack code I wrote in the App.xaml.cs like this:
private void App_BackRequested(object sender, BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
return;
// Navigate back if possible, and if the event has not
// already been handled .
if (rootFrame.CanGoBack && e.Handled == false)
{
e.Handled = true;
rootFrame.GoBack();
}
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
//#if DEBUG
// if (System.Diagnostics.Debugger.IsAttached)
// {
// this.DebugSettings.EnableFrameRateCounter = true;
// }
//#endif
Frame rootFrame = Window.Current.Content as Frame;
Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested += App_BackRequested;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
I add my data to the ObservableCollection in the OnNavigatedTo, this OnNavigatedTo will be called when it navi-back, so the count of my listview will not be 0.
Wish this helps you.

radgridview.TableElement.Update Doesn't work

I'm trying to update my gridview using an event on a contextMenu.
but it's not working..
here's my code:
RowFormatting
void dgItemList_RowFormatting(object sender, RowFormattingEventArgs e)
{
ItemModel rowModel = e.RowElement.RowInfo.DataBoundItem as ItemModel;
if (rowModel.Status == 2)
{
e.RowElement.ForeColor = Color.Red;
}
}
ClickEvent
void Deactivate_Click(object sender, EventArgs e)
{
GridViewRowInfo row = dgItemList.CurrentRow;
ItemModel rowModel = row.DataBoundItem as ItemModel;
if(UiHelpers.ShowConfirmForm("Do you want to Deactivate this Item?"))
{
ServiceResult result = _svc.UpdateItemStatus(rowModel.ItemID);
if(result.Successful)
{
UiHelpers.ShowSuccessForm(rowModel.Description + " was successfully deactivated!");
dgItemList.TableElement.Update(GridUINotifyAction.StateChanged);
}
}
}
I am usng the .TableElement.Update() to run the rowFormatting.. however it isn't working... the function UpdateItemStatus just change the status of an item to 2. I am really new to this so bear with me.
I'm using C#, and Telerik.
Besides making sure the Status is set, I would also recommend using the
row.InvalidateRow()
method, which will invalidate only one row, while the TableElement.Update is heavier update.
Besides, in the RowFormatting handler, you will also have to reset the introduced appearance modifications as the grid uses virtualization and elements are being reused during operations like scrolling, filtering, etc:
void radGridView1_RowFormatting(object sender, RowFormattingEventArgs e)
{
if (e.RowElement.RowInfo.Cells[0].Value.ToString().Contains("3"))
{
e.RowElement.DrawFill = true;
e.RowElement.BackColor = Color.Yellow;
e.RowElement.GradientStyle = GradientStyles.Solid;
}
else
{
e.RowElement.ResetValue(LightVisualElement.DrawFillProperty, ValueResetFlags.Local);
e.RowElement.ResetValue(LightVisualElement.BackColorProperty, ValueResetFlags.Local);
e.RowElement.ResetValue(LightVisualElement.GradientStyleProperty, ValueResetFlags.Local);
}
More information on row formattig can be found here: link. And here you can read about the UI Virtualization: link

C# editable listview

Yesterday I try to implement a new listview that support sub-item edit, my solution is to show a textbox when double click the sub-item. The key code as following:
protected override void OnDoubleClick(EventArgs e)
{
Point pt = this.PointToClient(Cursor.Position);
ListViewItem curItem;
int subItemIndex = GetSubItemAt(pt.X, pt.Y, out curItem);
DoubleClickEventArgs args = new DoubleClickEventArgs(subItemIndex);
base.OnDoubleClick(args);
if (subItemIndex>=0 && !args.Cancel)
{
//StartEdit(...);
}
}
public void EndEdit(bool acceptChanges)
{
//validation
.................
.................
AfterSubItemEventArgs e = new AfterSubItemEventArgs(this.SelectedItems[0], m_editSubItemIndex, this.SelectedItems[0].SubItems[m_editSubItemIndex].Text, m_textbox.Text, false);
OnAfterSubItemEdit(e);
if (e.Cancel)
{
//....
}
else
{
//set new value
}
m_textbox.Visible = false;
m_editSubItemIndex = -1;
}
OnAfterSubItemEdit is a event that user can do some validations or other operations. I add a check in this method, if the new value exist, I will show a messagebox to user firstly, then hide the textbox. But now, the problem comes, when i move the mouse, the listview items can be selected, I don't how to solve this issue, I tried my best to find out the way, but failed. So, please help me!
Listview has a LabelEdit property; when you set it "true", then in an event handler you can call Listview.Items[x].BeginEdit(), and edit an item. As an example, you can handle ListView.DoubleClick event and call BeginEdit right there:
private void Form1_Load(object sender, System.EventArgs e)
{
listView1.LabelEdit = true;
}
private void listView1_DoubleClick(object sender, System.EventArgs e)
{
if(this.listView1.SelectedItems.Count==1)
{
this.listView1.SelectedItems[0].BeginEdit();
}
}
The problem is that your form still calls the DoubleClick event whether the value exists or not. Add appropriate condition before calling base DoubleClick in your code, i.e.:
if(!new value exists)
base.OnDoubleClick(args);

Preventing multiple repeat selection of synchronized Controls?

The working code sample here synchronizes (single) selection in a TreeView, ListView, and ComboBox via the use of lambda expressions in a dictionary where the Key in the dictionary is a Control, and the Value of each Key is an Action<int>.
Where I am stuck is that I am getting multiple repetitions of execution of the code that sets the selection in the various controls in a way that's unexpected : it's not recursing : there's no StackOverFlow error happening; but, I would like to figure out why the current strategy for preventing multiple selection of the same controls is not working.
Perhaps the real problem here is distinguishing between a selection update triggered by the end-user and a selection update triggered by the code that synchronizes the other controls ?
Note: I've been experimenting with using Delegates, and forms of Delegates like Action<T>, to insert executable code in Dictionaries : I "learn best" by posing programming "challenges" to myself, and implementing them, as well as studying, at the same time, the "golden words" of luminaries like Skeet, McDonald, Liberty, Troelsen, Sells, Richter.
Note: Appended to this question/code, for "deep background," is a statement of how I used to do things in pre C#3.0 days where it seemed like I did need to use explicit measures to prevent recursion when synchronizing selection.
Code : Assume a WinForms standard TreeView, ListView, ComboBox, all with the same identical set of entries (i.e., the TreeView has only root nodes; the ListView, in Details View, has one Column).
private Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();
private void Form1_Load(object sender, EventArgs e)
{
// add the Controls to be synchronized to the Dictionary
// with appropriate Action<int> lambda expressions
ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));
// optionally install event handlers at run-time like so :
// treeView1.AfterSelect += (object obj, TreeViewEventArgs evt)
// => { synchronizeSelection(evt.Node.Index, treeView1); };
// listView1.SelectedIndexChanged += (object obj, EventArgs evt)
// => { if (listView1.SelectedIndices.Count > 0)
// { synchronizeSelection(listView1.SelectedIndices[0], listView1);} };
// comboBox1.SelectedValueChanged += (object obj, EventArgs evt)
// => { synchronizeSelection(comboBox1.SelectedIndex, comboBox1); };
}
private void synchronizeSelection(int i, Control currentControl)
{
foreach(Control theControl in ControlToAction.Keys)
{
// skip the 'current control'
if (theControl == currentControl) continue;
// for debugging only
Console.WriteLine(theControl.Name + " synchronized");
// execute the Action<int> associated with the Control
ControlToAction[theControl](i);
}
}
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
synchronizeSelection(e.Node.Index, treeView1);
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
// weed out ListView SelectedIndexChanged firing
// with SelectedIndices having a Count of #0
if (listView1.SelectedIndices.Count > 0)
{
synchronizeSelection(listView1.SelectedIndices[0], listView1);
}
}
private void comboBox1_SelectedValueChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex > -1)
{
synchronizeSelection(comboBox1.SelectedIndex, comboBox1);
}
}
background : pre C# 3.0
Seems like, back in pre C# 3.0 days, I was always using a boolean flag to prevent recursion when multiple controls were updated. For example, I'd typically have code like this for synchronizing a TreeView and ListView : assuming each Item in the ListView was synchronized with a root-level node of the TreeView via a common index :
// assume ListView is in 'Details View,' has a single column,
// MultiSelect = false
// FullRowSelect = true
// HideSelection = false;
// assume TreeView
// HideSelection = false
// FullRowSelect = true
// form scoped variable
private bool dontRecurse = false;
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
if(dontRecurse) return;
dontRecurse = true;
listView1.Items[e.Node.Index].Selected = true;
dontRecurse = false;
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if(dontRecurse) return
// weed out ListView SelectedIndexChanged firing
// with SelectedIndices having a Count of #0
if (listView1.SelectedIndices.Count > 0)
{
dontRecurse = true;
treeView1.SelectedNode = treeView1.Nodes[listView1.SelectedIndices[0]];
dontRecurse = false;
}
}
Then it seems, somewhere around FrameWork 3~3.5, I could get rid of the code to suppress recursion, and there was was no recursion (at least not when synchronizing a TreeView and a ListView). By that time it had become a "habit" to use a boolean flag to prevent recursion, and that may have had to do with using a certain third party control.
I believe your approach is totally fine. If you want something a little more advanced, see Rein in runaway events with the "Latch", which allows for
void TabControl_TabSelected(object sender, TabEventArgs args)
{
_latch.RunLatchedOperation(
delegate
{
ContentTab tab = (ContentTab)TabControl.SelectedTab;
activatePresenter(tab.Presenter, tab);
});
}
Note: I always assumed an SO user should never answer their own question. But, after reading-up on SO-Meta on this issue, I find it's actually encouraged. Personally, I would never vote on my own answer as "accepted."
This "new solution" uses a strategy based on distinguishing between a control being updated as a result of end-user action, and a control being updated by synchronizing code: this issue was mentioned, as a kind of "rhetorical question," in the original question.
I consider this an improvement: it works; it prevents multiple update calls; but, I also "suspect" it's still "not optimal": appended to this code example is a list of "suspicions."
// VS Studio 2010 RC 1, tested under Framework 4.0, 3.5
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace SynchronizationTest_3
{
public partial class Form1 : Form
{
private readonly Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();
// new code : keep a reference to the control the end-user clicked
private Control ClickedControl;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));
// new code : screen out redundant calls generated by other controls
// being updated
treeView1.AfterSelect += (obj, evt)
=>
{
if (treeView1 == ClickedControl) SynchronizeSelection(evt.Node.Index);
};
listView1.SelectedIndexChanged += (obj, evt)
=>
{
if (listView1.SelectedIndices.Count > 0 && listView1 == ClickedControl)
{
SynchronizeSelection(listView1.SelectedIndices[0]);
}
};
comboBox1.SelectedValueChanged += (obj, evt)
=>
{
if (comboBox1 == ClickedControl) SynchronizeSelection(comboBox1.SelectedIndex);
};
// new code here : all three controls share a common MouseDownHandler
treeView1.MouseDown += SynchronizationMouseDown;
listView1.MouseDown += SynchronizationMouseDown;
comboBox1.MouseDown += SynchronizationMouseDown;
// trigger the first synchronization
ClickedControl = treeView1;
SynchronizeSelection(0);
}
// get a reference to the control the end-user moused down on
private void SynchronizationMouseDown(object sender, MouseEventArgs e)
{
ClickedControl = sender as Control;
}
// revised code using state of ClickedControl as a filter
private void SynchronizeSelection(int i)
{
// we're done if the reference to the clicked control is null
if (ClickedControl == null) return;
foreach (Control theControl in ControlToAction.Keys)
{
if (theControl == ClickedControl) continue;
// for debugging only
Console.WriteLine(theControl.Name + " synchronized");
ControlToAction[theControl](i);
}
// set the clicked control to null
ClickedControl = null;
}
}
}
Why I "suspect" this is not optimal:
the idiosyncratic behavior of WinForms controls has to be taken into account: for example, the ListView Control fires its Selected### Events before it fires a Click Event: ComboBox and TreeView fire their Click Events before their SelectedValueChanged and AfterSelect Events respectively: so had to experiment to find that using 'MouseDown would work the same across all three controls.
a "gut level" feeling that I've gone "too far" out on "some kind of limb" here: a sense a much simpler solution might be possible.

Categories