0

I would like to train in c# and I created a WPF project in which are displayed some persons(with each a name and a work), and by selecting a field ('name' or 'work') and a search string, I would get all the persons filtered. for example, the field is 'name', the string is 'tin' and the only person whose name contains 'tin' is 'tintin'. I wrote all the program but I get a null reference error with the search engine(the parameterized class whose purpose is to filter the persons).

here is the person class:

namespace VueEnPremier.Model
{
    public class Person : ViewModelBase
    {
        public string Name { get; set; }

        public string Work { get; set; }

    }
}

here is my view:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:VueEnPremier"
        xmlns:vm="clr-namespace:VueEnPremier.ViewModel"
        xmlns:System="clr-namespace:System;assembly=mscorlib" x:Class="VueEnPremier.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <!--<vm:VMSearchName x:Key="ByName" />
        <vm:VMSearchWork x:Key="ByWork"/>-->
        <vm:VMSearch x:Key="ByName" NameOfTypeToSearch="Name"/>
        <vm:VMSearch x:Key="ByWork" NameOfTypeToSearch="Work"/>

    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource Locator}}">
        <StackPanel>
            <StackPanel x:Name="liste">
                <ComboBox x:Name="comboBox" Width="150" HorizontalAlignment="Left" Margin="10,10,0,10" 
                          SelectedItem="{Binding Main.SearchEngine, Mode=OneWayToSource}">
                    <ComboBoxItem Content="{StaticResource ByName}" HorizontalAlignment="Center" IsSelected="True"/>
                    <ComboBoxItem Content="{StaticResource ByWork}" HorizontalAlignment="Center"/>

                </ComboBox>
                <TextBox Text="{Binding Main.ToFind, Mode=OneWayToSource}"></TextBox>
                <ItemsControl 
                    ItemsSource="{Binding ElementName=comboBox,Path=SelectedItem.(vm:VMSearch.PersonsFiltered)}"/>
            </StackPanel>

        </StackPanel>
    </Grid>

</Window>

here is my search engine:

namespace VueEnPremier.ViewModel
{
    public class VMSearch : ViewModelBase
    {
        public string ToFind { get; set; }

        public string NameOfTypeToSearch { get; set; }


        public VMSearch(string nameOfTypeToSearch)
        {
            NameOfTypeToSearch = nameOfTypeToSearch;
        }

        public VMSearch() { }

        protected List<Person> Persons { set; get; }


        public void UpdatesDatas(List<Person> persons, string toFind)
        {
            Persons = persons;
            ToFind = toFind;

            RaisePropertyChanged(() => this.PersonsFiltered);

        }

        public override string ToString() => NameOfTypeToSearch;

        public List<Person> PersonsFiltered
        {
            get
            {
                return Persons?.Where(c => (c.GetType().GetProperty(NameOfTypeToSearch).GetValue(c,null) as string).Contains(ToFind)).ToList();
            }
        }

    }
}

The getter of PersonsFiltered is a little tricky, it gets the value of a property from the string name of this property ("Name" --> c.Name is obtained). The point is that in XAML, the combobox contains directly the 2 search engines, and the selectedItem property should be binded (one way to source) to the SearchEngine property of the viewmodel: all the logic stands in the bindings.

and finally here is my viewmodel:

using System.Collections.Generic;
using System.Windows.Documents;
using System.Windows.Media.Animation;
using GalaSoft.MvvmLight;
using VueEnPremier.Model;

namespace VueEnPremier.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {

        public List<Person> AllNames { get; set; }

        #region ToFind

        private string _toFind = string.Empty;


        public string ToFind
        {
            get => _toFind;
            set
            {
                _toFind = value;
                RaisePropertyChanged(()=>this.ToFind);
                SearchEngine.UpdatesDatas(AllNames,value);
            }
        }



        #endregion


        #region SearchEngine

        private VMSearch _searchEngine;


        public VMSearch SearchEngine
        {
            get { return _searchEngine; }
            set
            {
                if (value != _searchEngine)
                    _searchEngine = value;
                RaisePropertyChanged("SearchEngine");
            }
        }
        #endregion


        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            ////if (IsInDesignMode)
            ////{
            ////    // Code runs in Blend --> create design time data.
            ////}
            ////else
            ////{
            ////    // Code runs "for real"
            ////}

            Fill();

        }

        private void Fill()
        {
            AllNames = new List<Person>();
            AllNames.Add(new Person() {Name = "sanzot", Work = "boucher"});
            AllNames.Add(new Person() {Name = "buck dany", Work = "pilote"});
            AllNames.Add(new Person() {Name = "lefuneste", Work = "cuistre"});
            AllNames.Add(new Person() {Name = "tintin", Work = "reporter"});
            AllNames.Add(new Person() {Name = "blake", Work = "pilote"});

        }
    }
}

I use MVVM light. The error I get is, at startup :

System.NullReferenceException HResult=0x80004003 Message=La référence d'objet n'est pas définie à une instance d'un objet.
Source=VueEnPremier Arborescence des appels de procédure : à VueEnPremier.ViewModel.MainViewModel.set_ToFind(String value) dans C:\Users\osain\source\repos\MVVMBook\VueEnPremier\ViewModel\MainViewModel.cs :ligne 38

it says that in the line with 'searchEngine.updateDatas(AllNames,value);' the searchEngine variable is null.

thank you.

lolveley
  • 1,659
  • 2
  • 18
  • 34
  • Apparently you never assign a value to the SearchEngine property. It's unclear why you think it should not be null. – Clemens Feb 07 '20 at 06:41
  • it should not be null because the combobox' selected item is bound to the property 'Search Engine', which should be filled by a VMSearch ressource. Moreover, I had a look at the other question quoted, and it was said that in XAML, the order is important, but here the combobox is declared before the textbox, so it seems the update of the SearchEngine property is not done before the parsing of the textbox. It's interesting, not explained in the quoted SO question, and my question should not be closed – lolveley Feb 07 '20 at 08:32
  • But what is `Main` in `SelectedItem="{Binding Main.SearchEngine}"`? Look out for data binding error messages in the Output Window in Visual Studio. – Clemens Feb 07 '20 at 08:38
  • Main is an instance of MainViewModel prodided by the MVVM light's locator. – lolveley Feb 07 '20 at 08:42
  • I will have a look at the binding errors. – lolveley Feb 07 '20 at 08:44
  • Be aware that bindings are established after the entire view is initialized. You can not safely assume that the property is immediately initialized. Just check its value for null before using it. As usual. – Clemens Feb 07 '20 at 08:47

0 Answers0