0

I have a problem for which I'm searching an explanation. It's similar to what's been discussed in WPF ComboBox SelectedItem Set to Null on TabControl Switch, but it's involving a lesser degree of binding and so should be open to simpler solutions. What I'm describing below is a simplified case I've built to reproduce and try to understand why the problem is arising.

So, the project is based on MVVM, and the main window has just a button labelled "Search", declared as follows:

<Button Margin="50,0,0,0" Width="150" Height="40" Content="Search" HorizontalAlignment="Left" Command="{Binding UpdateViewCommand}" CommandParameter="Search"/>

The code is bound to UpdateView :ICommand that, is defined as follows:

class UpdateViewCommand : ICommand
{
    private MainViewModel viewModel;

    public UpdateViewCommand(MainViewModel viewModel)
    {
        this.viewModel = viewModel;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        if (parameter.ToString() == "Search")
        {
            viewModel.SelectedViewModel = new SearchViewModel();
        }
    }
}

This view overlaps with the main one in the upper part, leaving the "Search" button visible, as shown in the picture below:

Final view

The view includes a ComboBox and a "Go" button, declared as:

    <ComboBox Name="SearchCriterion" Canvas.Left="128" Canvas.Top="14" Height="22" Width="257" Background="#FF66CCFF" BorderBrush="Black" SelectedIndex="0"
              SelectedItem="{Binding QueryType, Mode=OneWayToSource}">
        <ComboBoxItem FontFamily="Calibri" FontSize="14" Background="#FF66CCFF">
            Author
        </ComboBoxItem>
        <ComboBoxItem FontFamily="Calibri" FontSize="14" Background="#FF66CCFF">
            Title
        </ComboBoxItem>
    </ComboBox>
    <Button Name="SearchButton" Height="22" Content="Go" Canvas.Left="390" Canvas.Top="44" Width="45" BorderBrush="Black"
            FontFamily="Calibri" FontSize="14" Background="#FF0066FF" Command="{Binding ExecQueryCmd}" Foreground="White"/>

All the button does is getting the ComboBoxItem value bound in the ComboBox declaration through the variable QueryType and print it. QueryType is declared as:

    private ComboBoxItem _queryType = new ComboBoxItem();
    public  ComboBoxItem QueryType
    {
        get { return _queryType; }
        set
        {
            Globals.mylog.Trace("In SearchViewModel.QueryType");
            _queryType = value;
            OnPropertyChanged(nameof(QueryType));
            
        }
    }

Assuming this is clear, here is the problem I see. I start the program, click on "Search" and the SearchView appears. I play with the ComboBox, click "Go" and all is fine. I can do this several times, no problem.

Now I click on "Search" again. No apparent change (the view is already there), but if I click on "Go" an exception is raised because the variable is null (I'm running under Visual Studio, so I can easily check). Note that if, instead of clicking "Go" right after clicking on "Search", I click on the ComboxBox and change its value before, everything works fine.

Can anyone explain me why this is happening, and how I can solve it?

Thanks

  • 3
    Hi, not sure what is causing the null exception without seeing exactly what happens when you click "search" and "go" but you definitely shouldn't have objects of type `ComboBoxItem` inside your viewModel, it should be the value behind it instead. – Ostas Feb 05 '21 at 22:26
  • @Ostas: I've edited my question and included the definition of UpdateViewCommand, that is invoked when I click the "Search" button. Clicking on "Go" does something similar and ends up in a method that simply prints QueryType.Content. Hope this may give you some clue. Irrespective of this, can you please elaborate on you point, i.e. that I should have "the value behind it" in my viewModel? Do you mean that, since the items in my ComboBox are strings, I should declare QueryType as string? – Giovanni Nieddu Feb 06 '21 at 20:16
  • You should post enough code to reproduce the issue. You didn't even mention which variable is null when the exception is raised. I'm guessing it's QueryType since you don't have the issue if you change the value in the combobox. You should be able to see with the stack trace at which line the null exception is raised. You can check the "Zen of the viewmodel" paragraph in this article [here](https://www.reactiveui.net/docs/handbook/view-models/#the-zen-of-the-viewmodel) (the rest of the article is about something else). – Ostas Feb 06 '21 at 20:40
  • Basically the goal of mvvm is to not be tied to a display technology so using `ComboBoxItem` inside a ViewModel definitely goes against the principle of MVVM. So the combobox should be bound to a `List` or `` if you need to add or remove items and have it updated on the combobox, and QueryType should be string. – Ostas Feb 06 '21 at 20:41
  • @Ostast - thanks for the suggestions about MVVM and the article. About posting anough code to reproduce the issue, the best thing I can do without making the question too long is to share this link to [my code](https://drive.google.com/file/d/1Z9VG9vRO7PGLpHPNiVUWAWIIfAjzO1dx/view?usp=sharing) if you want to have a look. – Giovanni Nieddu Feb 07 '21 at 11:17

1 Answers1

1

You never explicitly assigned a value to QueryType in the constructor of SearchViewModel, so the value in querytype was depending on the UI to update it. A better way is to have the selectedvalue come from the viewmodel (and not have ui elements in tour viewmodels as I mentionned in the comments).

What I changed to make it works: In SearchViewModel:

    /// <summary>
    /// Selected option to search by (it is now a string)
    /// </summary>
    private string _queryType;
    public  string QueryType
    {
        get { return _queryType; }
        set
        {
            Globals.mylog.Trace("In SearchViewModel.QueryType");
            _queryType = value;
            OnPropertyChanged(nameof(QueryType));
            
        }
    }

    /// <summary>
    /// List of options to search by
    /// </summary>
    public ObservableCollection<string> Queries { get; set; }

    public SearchViewModel()
    {
        Globals.mylog.Trace("In SearchViewModel");

        //Initialization ofthe list of options
        Queries = new ObservableCollection<string> { "Author", "Title" };
        //Initialization of the selected item
        this.QueryType = Queries.FirstOrDefault();
        ExecQueryCmd = new RelayCommand(ExecuteQuery, CanExecuteQuery);
    }

In SearchView:

<--The combobox is now bound to the list in the ViewModel(the data is stored in the viewmodels and the view is only responsible for displaying it) -->
<Canvas Width="517" Height="580" Background="#FFCCFF99">
    <ComboBox Name="SearchCriterion" Canvas.Left="128" Canvas.Top="14" Height="22" Width="257" ItemsSource="{Binding Queries}" Background="#FF66CCFF" BorderBrush="Black"
              SelectedItem="{Binding QueryType, Mode=TwoWay}">
        <ComboBox.ItemContainerStyle>
            <Style BasedOn="{StaticResource {x:Type ComboBoxItem}}" TargetType="{x:Type ComboBoxItem}">
                <Setter Property="FontFamily" Value="Calibri"/>
                <Setter Property="FontSize" Value="14"/>
                <Setter Property="Background" Value="#FF66CCFF"/>
            </Style>
        </ComboBox.ItemContainerStyle>
    </ComboBox>
    <Button Name="SearchButton" Height="22" Content="Go" Canvas.Left="390" Canvas.Top="44" Width="45" BorderBrush="Black"
            FontFamily="Calibri" FontSize="14" Background="#FF0066FF" Command="{Binding ExecQueryCmd}" Foreground="White"/>
</Canvas>
Ostas
  • 839
  • 7
  • 11
  • thanks a lot! Your solution clarified a number of things. Can you perhaps clarify a last one. I was trying to follow your suggestion of using QueryType as string. When I attempted at that (but still using my original XAML), the value I got when printing QueryType was something like: System.Windows.Controls.ComboBoxItem: Author. This doesn't happen with your code, is it because of the – Giovanni Nieddu Feb 07 '21 at 21:23
  • 1
    Glad that helped :) It's because when I replaced QueryType as a string, I now populate the combobox with databinding (`ItemsSource={Binding Queries}`) so SelectedItem is also of type string. In your xaml, the items were Comboboxitem so the selectedItem binding failed when trying to convert Comboboxitem to string (the type of `QueryType`). The ` – Ostas Feb 07 '21 at 22:32
  • Thanks again, @Ostas! – Giovanni Nieddu Feb 08 '21 at 20:29