5

I am using a ComboBox to insert a text template into a RichEdit control (the name of the template is in the picklist for the ComboBox.)

It all works great except when the user selects the same value in the list again. Then the SelectionChanged is not firing. That makes sense based on the name of the event (SelectionChanged), but I need to know that the value was re-selected so I can insert it again.

Is there a way to know that an item was re-selected from the ComboBox? (Or a better control to use?)

I tried using the DropDownClosed event, but that fires even if the item was not re-selected. (They open the drop down then click on another control.)

Vaccano
  • 78,325
  • 149
  • 468
  • 850

5 Answers5

3

It sounds like the way you are using your combo box isn't consistent with normal usage. Would it work to have a button next to the combo box that inserted the selected template. I think that behavior would work better for users who are familiar with the behavior of Google search with search sugestions

BenCamps
  • 1,694
  • 15
  • 25
2

I had the same question and I finally found the answer:

You need to handle BOTH the SelectionChanged event and the DropDownClosed like this:

In XAML:

<ComboBox Name="cmbSelect" SelectionChanged="ComboBox_SelectionChanged" DropDownClosed="ComboBox_DropDownClosed">
  <ComboBoxItem>1</ComboBoxItem>
  <ComboBoxItem>2</ComboBoxItem>
  <ComboBoxItem>3</ComboBoxItem>
</ComboBox>

In C#:

private bool handle = true;
private void ComboBox_DropDownClosed(object sender, EventArgs e) {
  if(handle)Handle();
  handle = true;
}

private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
  ComboBox cmb = sender as ComboBox;
  handle = !cmb.IsDropDownOpen;
  Handle();
}

private void Handle() {
  switch (cmbSelect.SelectedItem.ToString().Split(new string[] { ": " }, StringSplitOptions.None).Last())
  { 
      case "1":
          //Handle for the first combobox
          break;
      case "2":
          //Handle for the second combobox
          break;
      case "3":
          //Handle for the third combobox
          break;
  }
}
deltonio2
  • 1,719
  • 1
  • 23
  • 37
1

You'll need to provide some code to show what you're trying to do. If an item is already selected, why would the RichEdit control's template not already be set anyway?
When dealing with WPF it helps to get familiar with binding to view models, not just for things like ItemsSource but also SelectedItem. Given the scenario you describe I would use a binding for the SelectedItem to the View Model then bind the RichEdit control's template to the same View Model property, using a value converter if necessary. This way you don't need to mess around with click events and the like. Provided the View Model's property is a DependencyProperty or fires a PropertyChanged event (see INotifyPropertyChanged) your RichEdit control's template should automatically reflect the selection in the drop-down.

** Edit ** Based on your comments I'm assuming the behaviour you want is to set text based on a combo selection but allow the user to customise that text. However, if edited they should be able to re-select the combo value to reset the text. The trouble is that if the item is already selected then there is no event fired to hook into. The solution is that if the text contents change, this should de-select any combo selection (since the combo no longer reflects the contents of the text box.) Bindings can manage this quite nicely:

This example view uses a TextBox for simplicity:

<Window x:Class="UISample.UITemplateSample"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="UITemplateSample" Height="300" Width="300">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <ComboBox ItemsSource="{Binding Path=Templates}" SelectedItem="{Binding Path=SelectedTemplate}" DisplayMemberPath="Name"/>
    <TextBox Grid.Row="1" AcceptsReturn="True" TextWrapping="Wrap" Text="{Binding Path=Text}"/>
</Grid>

The ViewModel:

    class TemplateSampleViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<TextTemplate> Templates
    {
        get;
        private set;
    }

    private TextTemplate _selectedTemplate;
    public TextTemplate SelectedTemplate
    {
        get{ return _selectedTemplate; }
        set
        {
            if ( _selectedTemplate == value )
                return;
            _selectedTemplate = value;
            if (_selectedTemplate != null)
                Text = _selectedTemplate.TemplateText;
            firePropertyChanged("SelectedTemplate");
        }
    }

    private string _text;
    public string Text
    {
        get { return _text; }
        set
        {
            if ( _text == value )
                return;

            _text = value;
            firePropertyChanged( "Text" );
            var matchingTemplate = Templates.FirstOrDefault( t => t.TemplateText == _text );
            SelectedTemplate = matchingTemplate;

        }
    }

    public TemplateSampleViewModel(IEnumerable<TextTemplate> templates)
    {
        Templates = new ObservableCollection<TextTemplate>(templates);
    }

    private void firePropertyChanged(string propertyName)
    {
        if ( PropertyChanged != null )
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

And wiring it up:

            var viewModel = new TemplateSampleViewModel(new[]
                                                        {
                                                            new TextTemplate {Name = "First", TemplateText = "This is the first item"},
                                                            new TextTemplate {Name = "Second", TemplateText = "This is the second item"},
                                                            new TextTemplate {Name = "Third", TemplateText = "This is the third item"},
                                                        });
        var test = new UITemplateSample {DataContext = viewModel};
        test.Show();

This binds the combo box, then as items are selected, the text box is updated automatically. When the text box contents change, the template is inspected to see if it still matches and if not, the combo item is de-selected. If the entry matches a template then that template is selected automatically.

Steve Py
  • 26,149
  • 3
  • 25
  • 43
  • When I said "Text Template" I was not referring to a WPF Template. I mean that I insert actual text (a pre-set text "template") into the RichTextBox. I am not really binding to anything on this one (aside from the list in the ComboBox). When an item in the combobox is selected, I insert the text into the rich textbox. But I need to insert it again if the same item in the combo box is selected. – Vaccano Jan 14 '13 at 21:32
  • The same principle applies. I'll update the answer to outline how the binding can be applied. – Steve Py Jan 15 '13 at 01:26
1

The best I could find was to clear out the selected value as the dropdown opens. This is not ideal as the user does not get to keep their previous location as a point of reference (good for long lists). But it is the best solution I can find.

Here is the code I used:

ctor()
{
     myComboBox.DropDownOpened += OnDropDownOpened;
}

private void OnDropDownOpened(object sender, EventArgs e)
{
   var comboBox = ((ComboBox)sender);
   comboBox.SelectedItem = null;
}
Vaccano
  • 78,325
  • 149
  • 468
  • 850
1

I would use a list box. The user does not need to open it. Instead he can immediately select an item from the list. Also I would not attach this function to the SelectionChanged event but to the MouseDoubleClick event. This allows selecting and reselecting an item easily. Also I would add a button, which triggers the same function. The double click should only be a kind of shortcut for this button, also the button can have a descriptive text and/or an icon, which makes it even more intuitive.

SelectionChanged will fire even when the user moves up and down using the arrow keys, but the function should not be triggered then.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188