Is there an easy method to prompt the user to confirm a combo box selection change and not process the change if the user selected no?
We have a combo box where changing the selection will cause loss of data. Basically the user selects a type, then they are able to enter attributes of that type. If they change the type we clear all of the attributes as they may no longer apply. The problem is that to under the selection you raise the SelectionChanged event again.
Here is a snippet:
if (e.RemovedItems.Count > 0)
{
result = MessageBox.Show("Do you wish to continue?",
"Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.No)
{
if (e.RemovedItems.Count > 0)
((ComboBox)sender).SelectedItem = e.RemovedItems[0];
else
((ComboBox)sender).SelectedItem = null;
}
}
I have two solutions, neither of which I like.
After the user selects 'No', remove the SelectionChanged event handler, change the selected item and then register the SelectionChanged event handler again. This means you have to hold onto a reference of the event handler in the class so that you can add and remove it.
Create a ProcessSelectionChanged boolean as part of the class. Always check it at the start of the event handler. Set it to false before we change the selection back and then reset it to true afterwards. This will work, but I don't like using flags to basically nullify an event handler.
Anyone have an alternative solution or an improvement on the ones I mention?
I found this good implementation.
private bool handleSelection=true;
private void ComboBox_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if (handleSelection)
{
MessageBoxResult result = MessageBox.Show
("Continue change?", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No)
{
ComboBox combo = (ComboBox)sender;
handleSelection = false;
combo.SelectedItem = e.RemovedItems[0];
return;
}
}
handleSelection = true;
}
source: http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html
Maybe create a class deriving from ComboBox, and override the OnSelectedItemChanged (Or OnSelectionChangeCommitted.)
Validating within the SelectionChanged event handler allows you to cancel your logic if the selection is invalid, but I don't know of an easy way to cancel the event or item selection.
My solution was to sub-class the WPF combo-box and add an internal handler for the SelectionChanged event. Whenever the event fires, my private internal handler raises a custom SelectionChanging event instead.
If the Cancel property is set on the corresponding SelectionChangingEventArgs, the event isn't raised and the SelectedIndex is reverted to its previous value. Otherwise a new SelectionChanged is raised that shadows the base event. Hopefully this helps!
EventArgs and handler delegate for SelectionChanging event:
public class SelectionChangingEventArgs : RoutedEventArgs
{
public bool Cancel { get; set; }
}
public delegate void
SelectionChangingEventHandler(Object sender, SelectionChangingEventArgs e);
ChangingComboBox class implementation:
public class ChangingComboBox : ComboBox
{
private int _index;
private int _lastIndex;
private bool _suppress;
public event SelectionChangingEventHandler SelectionChanging;
public new event SelectionChangedEventHandler SelectionChanged;
public ChangingComboBox()
{
_index = -1;
_lastIndex = 0;
_suppress = false;
base.SelectionChanged += InternalSelectionChanged;
}
private void InternalSelectionChanged(Object s, SelectionChangedEventArgs e)
{
var args = new SelectionChangingEventArgs();
OnSelectionChanging(args);
if(args.Cancel)
{
return;
}
OnSelectionChanged(e);
}
public new void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (_suppress) return;
// The selection has changed, so _index must be updated
_index = SelectedIndex;
if (SelectionChanged != null)
{
SelectionChanged(this, e);
}
}
public void OnSelectionChanging(SelectionChangingEventArgs e)
{
if (_suppress) return;
// Recall the last SelectedIndex before raising SelectionChanging
_lastIndex = (_index >= 0) ? _index : SelectedIndex;
if(SelectionChanging == null) return;
// Invoke user event handler and revert to last
// selected index if user cancels the change
SelectionChanging(this, e);
if (e.Cancel)
{
_suppress = true;
SelectedIndex = _lastIndex;
_suppress = false;
}
}
}
In WPF dynamically set the object with
if (sender.IsMouseCaptured)
{
//perform operation
}
I do not believe using the dispatcher to post (or delay) a property update is a good solution, it is more of a workaround that is not really needed. The following solution i fully mvvm and it does not require a dispatcher.
First Bind the SelectedItem with an Explicit binding Mode. //this enables us to decide whether to Commit using the UpdateSource() method the changes to the VM or to Revert using the UpdateTarget() method in the UI.
Next, add a method to the VM that confirms if the change is allowed (This method can contain a service that prompts for user confirmation and returns a bool).
In the view code behind hook to the SelectionChanged event and update the Source (i.e., the VM) or the Target (i.e. the V) in accordance to whether the VM.ConfirmChange(...) method returned value as follows:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(e.AddedItems.Count != 0)
{
var selectedItem = e.AddedItems[0];
if (e.AddedItems[0] != _ViewModel.SelectedFormatType)
{
var comboBoxSelectedItemBinder = _TypesComboBox.GetBindingExpression(Selector.SelectedItemProperty); //_TypesComboBox is the name of the ComboBox control
if (_ViewModel.ConfirmChange(selectedItem))
{
// Update the VM.SelectedItem property if the user confirms the change.
comboBoxSelectedItemBinder.UpdateSource();
}
else
{
//otherwise update the view in accordance to the VM.SelectedItem property
comboBoxSelectedItemBinder.UpdateTarget();
}
}
}
}
This is an old question, but after struggling with the issue time and again I came up with this solution:
ComboBoxHelper.cs:
public class ComboBoxHelper
{
private readonly ComboBox _control;
public ComboBoxHelper(ComboBox control)
{
_control = control;
_control.PreviewMouseLeftButtonDown += _control_PreviewMouseLeftButtonDown; ;
_control.PreviewMouseLeftButtonUp += _control_PreviewMouseLeftButtonUp; ;
}
public Func<bool> IsEditingAllowed { get; set; }
public Func<object, bool> IsValidSelection { get; set; }
public Action<object> OnItemSelected { get; set; }
public bool CloseDropDownOnInvalidSelection { get; set; } = true;
private bool _handledMouseDown = false;
private void _control_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var isEditingAllowed = IsEditingAllowed?.Invoke() ?? true;
if (!isEditingAllowed)
{
e.Handled = true;
return;
}
_handledMouseDown = true;
}
private void _control_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (!_handledMouseDown) return;
_handledMouseDown = false;
var fe = (FrameworkElement)e.OriginalSource;
if (fe.DataContext != _control.DataContext)
{
//ASSUMPTION: Click was on an item and not the ComboBox itself (to open it)
var item = fe.DataContext;
var isValidSelection = IsValidSelection?.Invoke(item) ?? true;
if (isValidSelection)
{
OnItemSelected?.Invoke(item);
_control.IsDropDownOpen = false;
}
else if(CloseDropDownOnInvalidSelection)
{
_control.IsDropDownOpen = false;
}
e.Handled = true;
}
}
}
It can be used in a custom UserControl like this:
public class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
var helper = new ComboBoxHelper(MyComboBox); //MyComboBox is x:Name of the ComboBox in Xaml
helper.IsEditingAllowed = () => return Keyboard.Modifiers != Modifiers.Shift; //example
helper.IsValidSelection = (item) => return item.ToString() != "Invalid example.";
helper.OnItemSelected = (item) =>
{
System.Console.WriteLine(item);
};
}
}
This is independent of the SelectionChanged event, there are no side effects of the event firing more often than required. So others can safely listen to the event, e.g. to update their UI. Also avoided: "recursive" calls caused by resetting the selection from within the event handler to a valid item.
The assumptions made above regarding DataContext may not be a perfect fit for all scenarios, but can be easily adapted. A possible alternative would be to check, if the ComboBox is a visual parent of e.OriginalSource, which it isn't when an item is selected.
Related
I am creating custom transport controls.
I want to have a Visibility control for a custom Button which I have created. So I have created a Property for it. In that Property, I have used GetTemplateChild("CompactOverlayButton") as Button to get the particular button but it returns null.
Here is my code
public bool IsCompactOverlayButtonVisible
{
get
{
var compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
if (compactOverlayButton.Visibility == Visibility.Visible)
return true;
else
return false;
}
set
{
var compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
if (value)
compactOverlayButton.Visibility = Visibility.Visible;
else
compactOverlayButton.Visibility = Visibility.Collapsed;
}
}
But the same line of code returns proper value in OnApplyTemplate() function.
Here is my code for OnApplyTemplate()
protected override void OnApplyTemplate()
{
var compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
compactOverlayButton.Click += CompactOverlayButton_ClickAsync;
base.OnApplyTemplate();
}
IsCompactOverlayButtonVisible probably gets evaluated for the first time before OnApplyTemplate(), meaning that the first time it gets evaluated, the template hasn't been applied and the button doesn't exist yet. In OnApplyTemplate(), get the button and assign it to a private field.
private Button _compactOverlayButton;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
_compactOverlayButton.Click += CompactOverlayButton_ClickAsync;
}
And before you try to touch the button's properties, make sure it's not null.
public bool IsCompactOverlayButtonVisible
{
get
{
return _compactOverlayButton != null
&& _compactOverlayButton.Visibility == Visibility.Visible;
}
set
{
if (_compactOverlayButton != null)
{
compactOverlayButton.Visibility = value
? Visibility.Visible
: Visibility.Collapsed;
}
}
}
If something will set this value before the template is applied, for example if it's a public property of the control that may be set in XAML (which it sure looks like it is), you can't do it this way. You need to make it a regular dependency property, give it a PropertyChanged handler that updates the button's visibility if the button exists, and add a line in OnApplyTemplate() to update the actual button when you get your hands on it. Then it'll be usable as a target of a binding as well.
Update
And here's how you do that. This is the right way.
private Button _compactOverlayButton;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_compactOverlayButton = GetTemplateChild("CompactOverlayButton") as Button;
// Update actual button visibility to match whatever the dependency property value
// is, in case XAML gave us a value for it already.
OnIsCompactOverlayButtonVisibleChanged();
_compactOverlayButton.Click += CompactOverlayButton_Click;
// Secondly, just in case something in the XAML may change the button's visibility,
// put a watch on the property and update our dependency property to match when that
// changes.
var dpd = DependencyPropertyDescriptor.FromProperty(Button.VisibilityProperty, typeof(Button));
dpd.AddValueChanged(_compactOverlayButton, CompactOverlayButton_VisibilityChanged);
}
protected void CompactOverlayButton_VisibilityChanged(object sender, EventArgs args)
{
IsCompactOverlayButtonVisible = _compactOverlayButton.Visibility == Visibility.Visible;
}
private void CompactOverlayButton_Click(object sender, RoutedEventArgs e)
{
// ...whatever
}
#region IsCompactOverlayButtonVisible Property
public bool IsCompactOverlayButtonVisible
{
get { return (bool)GetValue(IsCompactOverlayButtonVisibleProperty); }
set { SetValue(IsCompactOverlayButtonVisibleProperty, value); }
}
public static readonly DependencyProperty IsCompactOverlayButtonVisibleProperty =
DependencyProperty.Register(nameof(IsCompactOverlayButtonVisible), typeof(bool), typeof(CustomMediaTransportControls),
new FrameworkPropertyMetadata(true,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
IsCompactOverlayButtonVisible_PropertyChanged));
protected static void IsCompactOverlayButtonVisible_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// It's a hassle to do stuff in a static method, so my dependency property
// snippet just creates a private instance method and calls it from the
// static handler.
(d as CustomMediaTransportControls).OnIsCompactOverlayButtonVisibleChanged();
}
private void OnIsCompactOverlayButtonVisibleChanged()
{
if (_compactOverlayButton != null)
{
// If the existing value is the same as the new value, this is a no-op
_compactOverlayButton.Visibility =
IsCompactOverlayButtonVisible
? Visibility.Visible
: Visibility.Collapsed;
}
}
#endregion IsCompactOverlayButtonVisible Property
I have found multiple questions about this problem on SO, however I still can't quite get a realiable solution. Here is what I came up with after reading the answers.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="300" Width="300" x:Name="this">
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Tabs, ElementName=this}" x:Name="TabControl"/>
</Window>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var tabs = new ObservableCollection<string> {"Tab1", "Tab2", "Tab3"};
Tabs = CollectionViewSource.GetDefaultView(tabs);
Tabs.CurrentChanging += OnCurrentChanging;
Tabs.CurrentChanged += OnCurrentChanged;
Tabs.MoveCurrentToFirst();
CurrentTab = tabs.First();
}
private void OnCurrentChanging(object sender, CurrentChangingEventArgs e)
{
//only show message box when tab is changed by user input
if (!_cancelTabChange)
{
if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No)
{
_cancelTabChange = true;
return;
}
}
_cancelTabChange = false;
}
private void OnCurrentChanged(object sender, EventArgs e)
{
if (!_cancelTabChange)
{
//Update current tab property, if user did not cancel transition
CurrentTab = (string)Tabs.CurrentItem;
}
else
{
//navigate back to current tab otherwise
Dispatcher.BeginInvoke(new Action(() => Tabs.MoveCurrentTo(CurrentTab)));
}
}
public string CurrentTab { get; set; }
public static readonly DependencyProperty TabsProperty = DependencyProperty.Register("Tabs", typeof(ICollectionView), typeof(MainWindow), new FrameworkPropertyMetadata(default(ICollectionView)));
public ICollectionView Tabs
{
get { return (ICollectionView)GetValue(TabsProperty); }
set { SetValue(TabsProperty, value); }
}
private bool _cancelTabChange;
}
Basically I want to display a confirmation message, when user navigates to different tab, and if he clicks "no" - abort the transition. This code does not work though. If you click multiple times on "Tab2", each time choosing "no" in message box, at some point it stops working: events stop triggering. Event will trigger again if you click on "Tab3", but if you choose "yes" it opens second tab and not third. I am having trouble figuring out wtf is going on. :)
Does anyone see a bug in my solution? Or is there an easier way to display a confirmation message, when user switches tabs? I am also willing to use any opensource tab control, which does have a proper SelectionChanging event. I could not find any though.
I am using .Net 4.0.
Edit:
If I comment the message box out:
private void OnCurrentChanging(object sender, CurrentChangingEventArgs e)
{
//only show message box when tab is changed by user input
if (!_cancelTabChange)
{
//if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No)
//{
Debug.WriteLine("Canceled");
_cancelTabChange = true;
return;
//}
}
_cancelTabChange = false;
}
Everything works fine. Weird.
This solution http://coderelief.net/2011/11/07/fixing-issynchronizedwithcurrentitem-and-icollectionview-cancel-bug-with-an-attached-property/
seems to work quite well with
<TabControl ... yournamespace:SelectorAttachedProperties.IsSynchronizedWithCurrentItemFixEnabled="True" .../>
private void OnCurrentChanging(object sender, CurrentChangingEventArgs e)
{
if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No)
{
e.Cancel = true;
}
}
public static class SelectorAttachedProperties
{
private static Type _ownerType = typeof(SelectorAttachedProperties);
#region IsSynchronizedWithCurrentItemFixEnabled
public static readonly DependencyProperty IsSynchronizedWithCurrentItemFixEnabledProperty =
DependencyProperty.RegisterAttached("IsSynchronizedWithCurrentItemFixEnabled", typeof(bool), _ownerType,
new PropertyMetadata(false, OnIsSynchronizedWithCurrentItemFixEnabledChanged));
public static bool GetIsSynchronizedWithCurrentItemFixEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsSynchronizedWithCurrentItemFixEnabledProperty);
}
public static void SetIsSynchronizedWithCurrentItemFixEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsSynchronizedWithCurrentItemFixEnabledProperty, value);
}
private static void OnIsSynchronizedWithCurrentItemFixEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Selector selector = d as Selector;
if (selector == null || !(e.OldValue is bool && e.NewValue is bool) || e.OldValue == e.NewValue)
return;
bool enforceCurrentItemSync = (bool)e.NewValue;
ICollectionView collectionView = null;
EventHandler itemsSourceChangedHandler = null;
itemsSourceChangedHandler = delegate
{
collectionView = selector.ItemsSource as ICollectionView;
if (collectionView == null)
collectionView = CollectionViewSource.GetDefaultView(selector);
};
SelectionChangedEventHandler selectionChangedHanlder = null;
selectionChangedHanlder = delegate
{
if (collectionView == null)
return;
if (selector.IsSynchronizedWithCurrentItem == true && selector.SelectedItem != collectionView.CurrentItem)
{
selector.IsSynchronizedWithCurrentItem = false;
selector.SelectedItem = collectionView.CurrentItem;
selector.IsSynchronizedWithCurrentItem = true;
}
};
if (enforceCurrentItemSync)
{
TypeDescriptor.GetProperties(selector)["ItemsSource"].AddValueChanged(selector, itemsSourceChangedHandler);
selector.SelectionChanged += selectionChangedHanlder;
}
else
{
TypeDescriptor.GetProperties(selector)["ItemsSource"].RemoveValueChanged(selector, itemsSourceChangedHandler);
selector.SelectionChanged -= selectionChangedHanlder;
}
}
#endregion IsSynchronizedWithCurrentItemFixEnabled
}
For some reason adding TabControl.Focus() fixes things:
private void OnCurrentChanged(object sender, EventArgs e)
{
if (!_cancelTabChange)
{
//Update current tab property, if user did not cancel transition
CurrentTab = (string)Tabs.CurrentItem;
}
else
{
//navigate back to current tab otherwise
Dispatcher.BeginInvoke(new Action(() =>
{
Tabs.MoveCurrentTo(CurrentTab);
TabControl.Focus();
}));
}
}
I still have no clue what on Earth is going on here. So I will gladly accept the answer, which sheds some light on this issue.
inside the tabControl_SelectionChanged event handler:
if (e.OriginalSource == tabControl) //if this event fired from your tabControl
{
e.Handled = true;
if (!forbiddenPage.IsSelected) //User leaving the tab
{
if (forbiddenTest())
{
forbiddenPage.IsSelected = true;
MessageBox.Show("you must not leave this page");
}
}
Note that setting forbiddenPage.IsSelected = true causes a loop and you reenter
this event handler. This time, however, we exit because the page selected IS the forbidden page.
private void MainTabControl_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ReasonBecauseLeaveTabItemIsForbidden)
{
if (MainTabControl.SelectedIndex == IndexOfTabItem)
{
MessageBox.Show(SomeMessageWhyLeaveTabItemIsForbidden);
}
MainTabControl.SelectedIndex = IndexOfTabItem;
}
}
IndexOfTabItem - index of TabItem that disabled for leaving.
He who must be obeyed requested that the application ask the user if they wish to leave the page so here is the slightly changed code:
private Object _selectedTab;
public Object SelectedTab
{
get
{
return _selectedTab;
}
set
{
if (
!(_selectedTab is ADR_Scanner.ViewModel.ConfigurationViewModel) ||
!_configurationViewModel.HasChanged ||
(System.Windows.Forms.MessageBox.Show("Are you sure you want to leave this page without saving the configuration changes", ADR_Scanner.App.Current.MainWindow.Title, System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Error) == System.Windows.Forms.DialogResult.Yes)
)
{
_selectedTab = value;
}
OnPropertyChanged("SelectedTab");
}
}
I think this small change does pretty much what you wanted.
There is a much easier solution. Add a binding to the selected item in the XAML:
<TabControl SelectedItem="{Binding SelectedTab}" ...
Then in the view model:
private Object _selectedTab;
public Object SelectedTab
{
get
{
return _selectedTab;
}
set
{
if (_selectedTab is ADR_Scanner.ViewModel.ConfigurationViewModel && _configurationViewModel.HasChanged)
{
System.Windows.Forms.MessageBox.Show("Please save the configuration changes", ADR_Scanner.App.ResourceAssembly.GetName().Name, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
}
else
{
_selectedTab = value;
}
OnPropertyChanged("SelectedTab");
}
}
Obviously you replace ADR_Scanner.ViewModel.ConfigurationViewModel with your own view model class. Lastly make sure you initialise _selectedTab in your constructor otherwise the TabControl will have no initial selection.
I'm looking for an elegant way to track changes between values for a combo box. What I'm looking to do is fire a custom event when the SelectionChanged event happens, but only for a specific value changes. This implies knowing what the initial value was. The event will only be fired when the initial value is changed from z. If the initial value is a, b, or c, the event will not be fired. But if the initial value was z, it will be fired.
Does anyone have an elegant way to solve this problem?
For this you will have to create a custom event handler and may be custom event args,
//Event Handler Class
public class SpecialIndexMonitor
{
public event EventHandler<SpecialIndexEventArgs> SpecialIndexSelected;
//Custom Function to handle Special Index
public void ProcessRequest(object sender, System.EventArgs e)
{
//Your custom logic
//Your code goes here
//Raise event
if(SpecialIndexSelected != null)
{
SpecialIndexEventArgs args = new SpecialIndexEventArgs{
SelectedIndex = ((ComboBox) sender).SelectedIndex;
};
SpecialIndexSelected(this, args);
}
}
}
//Custom Event Args
public class SpecialIndexEventArgs : EventArgs
{
//Custom Properties
public int SelectedIndex { get; set; } //For Example
//Default Constructor
public SpecialIndexEventArgs ()
{
}
}
Inside your form
//Hold previous value
private string _previousItem;
//IMPORTANT:
//After binding items to combo box you will need to assign,
//default selected item to '_previousItem',
//which will make sure SelectedIndexChanged works all the time
// Usage of Custom Event
private void comboBox1_SelectedIndexChanged(object sender,
System.EventArgs e)
{
string selectedItem = (string)comboBox1.SelectedItem;
if(string.Equals(_previousItem, )
switch(_previousItem)
{
case "z":
{
SpecialIndexMonitor spIndMonitor = new SpecialIndexMonitor();
spIndMonitor.SpecialIndexSelected +=
new EventHandler<SpecialIndexEventArgs>(SpecialIndexSelected);
break;
}
case "a":
case "b":
break;
}
_previousItem = selectedItem; //Re-Assign the current item
}
void SpecialIndexSelected(object sender, SpecialIndexEventArgs args)
{
// Your code goes here to handle the special index
}
Haven't compiled the code, but logically it should work for you.
I am trying to do something when double clicking an item in a ListBox. I have found this code for doing that
void listBox1_MouseDoubleClick(object sender, MouseEventArgs e)
{
int index = this.listBox1.IndexFromPoint(e.Location);
if (index != System.Windows.Forms.ListBox.NoMatches)
{
MessageBox.Show(index.ToString());
//do your stuff here
}
}
However, when i click on an item, the event isn't fired. The event is fired if i click in the ListBox below all the items.
I set the DataSource property of the ListBox to IList<MyObject>.
Any ideas?
Tried creating a form with a ListBox with MouseDown and DoubleClick events. As far as I can see, the only situation, when DoubleClick won't fire, is if inside the MouseDown you call the MessageBox.Show(...). In other cases it works fine.
And one more thing, I don't know for sure, if it is important, but anyway. Of course, you can get the index of the item like this:
int index = this.listBox1.IndexFromPoint(e.Location);
But this way is fine as well:
if (listBox1.SelectedItem != null)
...
Works for me, so I assume there might be something about the items in the list (custom? intercepting the event?) or the event is not properly wired up.
For example try this (complete Form1.cs):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public class MyObject {
public MyObject(string someValue) {
SomeValue = someValue;
}
protected string SomeValue { get; set; }
public override string ToString() {
return SomeValue;
}
}
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
var list = new List<MyObject> {
new MyObject("Item one"), new MyObject("Item two")
};
listBox1.DataSource = list;
}
private void listBox1_DoubleClick(object sender, EventArgs e) {
Debug.WriteLine("DoubleClick event fired on ListBox");
}
}
}
With the designer source file (Form1.Designer.cs) containing this:
private void InitializeComponent() {
this.listBox1 = new System.Windows.Forms.ListBox();
... // left out for brevity
this.listBox1.DoubleClick += new System.EventHandler(this.listBox1_DoubleClick);
As a test, create a new Forms base application through the templates, then add just the ListBox and define a class MyObject. See whether you observe the same or a different behavior.
Thank you for all replies. It now works. I solved it, like 26071986 said, with handling double click in the MouseDown handler by checking if e.Clicks is 1. If so, I call DoDragDrop, if not, I call the method that handles double click.
private void MouseDown_handler(object sender, MouseEventArgs e)
{
var listBox = (ListBox) sender;
if (e.Clicks != 1)
{
DoubleClick_handler(listBox1.SelectedItem);
return;
}
var pt = new Point(e.X, e.Y);
int index = listBox.IndexFromPoint(pt);
// Starts a drag-and-drop operation with that item.
if (index >= 0)
{
var item = (listBox.Items[index] as MyObject).CommaDelimitedString();
listBox.DoDragDrop(item, DragDropEffects.Copy | DragDropEffects.Move);
}
}
Here's what I used in the MouseDoubleClick event.
private void YourMethodForDoubleClick(object sender, MouseButtonEventArgs e)
{
Type sourceType = e.OriginalSource.GetType();
if (sourceType != typeof(System.Windows.Controls.TextBlock)
&& sourceType != typeof(System.Windows.Controls.Border))
return;
//if you get here, it's one of the list items.
DoStuff();
...
}
John: then it works. But i figured out that the event isn't fired because I am also handling the MouseDown event. I tried to remove the MouseDown handling, and then it works. Is there a smooth way to handle both those events? If not, I just have to find some other way to catch a double click through the MouseDown event.
Say I've got a button on a form that I want to disable if some condition is met. Is there a way to check for this condition inside the button's "IsEnabled" event handler and modify the enabled state such that setting the enabled state a second time does not trigger another call to the IsEnabled event handler?
Let me demonstrate:
private void ExportResults_IsEnabledChanged (object sender, DependencyPropertyChangedEventArgs e)
{
if (some condition)
{
uxExportResults.IsEnabled = false; // this will cause another call to the event handler, eventually resulting in a stack overflow
}
}
Assume I'm triggering the event elsewhere (which I am).
if (someCondition && uxExportResults.IsEnabled) { ... }
This will only disable your control if it's enabled.
Another option is to temporarily disable the event like so:
private void ExportResults_IsEnabledChanged (object sender, DependencyPropertyChangedEventArgs e)
{
if (some condition)
{
uxExportResults.IsEnabledChanged -= ExportResults_IsEnabledChanged;
try
{
uxExportResults.IsEnabled = false; // this will cause another call to the event handler, eventually resulting in a stack overflow
}
finally
{
uxExportResults.IsEnabledChanged += ExportResults_IsEnabledChanged;
}
}
}
Simplest solution is to check the value of IsEnabled before you set it.
private void ExportResults_IsEnabledChanged (object sender, DependencyPropertyChangedEventArgs e)
{
if (uxExportResults.IsEnabled == true)
{
uxExportResults.IsEnabled = false;
}
}
Also, if you have the ability to change the code for the button, IsEnabled should not send the event unless the value actually changes.
public bool IsEnabled
{
get { return isEnabled; }
set
{
if(isEnabled != value)
{
isEnabled = value;
IsEnabledChanged(this,args);
}
}
}