0
votes

I am trying to use a filter for a ListView upon and on initialization, the project is throwing a Null Pointer for the ListView, even though I am setting it in the Constructor and the binding. Here is my code:

 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var viewModel = new MainViewModel();
            this.ProjectListView.ItemsSource = viewModel.Projects;

And here is the XAML that refernces it.

<ListView x:Name="ProjectListView" Margin="0,0,10,0" ItemsSource="{Binding Projects}" FontSize="16" Foreground="Black">

I am not sure why the ItemSource is null if it is initialized in the Constructor. Is there a PropertyChanged binding I need to apply?

Here's most of my XAML

  <TextBox x:Name="FolderNameBox" Grid.Column="1" Background="White" Grid.Row="1" Grid.ColumnSpan="5" 
               Margin="0,0,287,654.333" VerticalContentAlignment="Center"
               Padding="6" FontSize="16"
               IsReadOnly="True"
               Text="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}">
    </TextBox>
    <TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="Search Projects"
        Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="LightGray" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268" GotFocus="TextBox_GotFocus" LostFocus="TextBox_LostFocus" TextChanged="FilterListView"/>
    <TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226" 
              ItemsSource="{Binding Path=Projects}">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
                <StackPanel Orientation="Horizontal" >
                    <Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
                    <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0" FontSize="16" Margin="5" IsReadOnly="True" PreviewMouseDoubleClick="SelectAll" LostFocus="TextBoxLostFocus"/>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    <Grid Grid.ColumnSpan="2" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="245,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="540">
        <ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
            <ListView x:Name="ProjectListView" Margin="0,0,10,0" ItemsSource="{Binding Projects}" FontSize="16" Foreground="Black">
                <ListView.ItemContainerStyle>
                    <Style TargetType="{x:Type ListViewItem}">
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="true">
                                <Setter Property="Background" Value="Transparent"/>
                                <Setter Property="BorderBrush" Value="Transparent"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="false">
                                <Setter Property="Background" Value="Transparent"/>
                                <Setter Property="BorderBrush" Value="Transparent"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </ListView.ItemContainerStyle>

ANd heres my base View Model

        private ObservableCollection<Project> projects;
    public ObservableCollection<Project> Projects
    {
        get { return projects; }
        set
        {
            if (value != projects)
            {
                projects = value;
                OnPropertyChanged("Projects");
            }
        }
    }

This is the Code Behind

        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
            MainViewModel model = new MainViewModel();
            model.BuildData();
            this.ProjectListView.ItemsSource = model.Projects;

        }

        private void FilterListView(object sender, TextChangedEventArgs e)
        {
            CollectionViewSource.GetDefaultView(this.ProjectListView.ItemsSource).Refresh();
        }

FilterListView is the TextChange method. It breaks on the first line. ProjectListVIew is registering as null but it shouldn't even be launched because it is dependent on a change in the TextBox.

Edit. Here's the code behind

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
            MainViewModel model = new MainViewModel();
            model.BuildData();
            this.ProjectListView.ItemsSource = model.Projects;

        }

        private void FilterListView(object sender, TextChangedEventArgs e)
        {
            CollectionViewSource.GetDefaultView(this.ProjectListView.ItemsSource).Refresh();
        }


        private void SelectAll(object sender, MouseButtonEventArgs e)
        {
            TextBox box = sender as TextBox;
            box.IsReadOnly = false;
            box.SelectAll();
        }

        private void TextBoxLostFocus(object sender, RoutedEventArgs e)
        {
            TextBox box = sender as TextBox;
            box.IsReadOnly = true;
        }

        private void TextBox_GotFocus(object sender, RoutedEventArgs e)
        {
            TextBox tb = (TextBox)sender;
            tb.Text = string.Empty;
            tb.Foreground = Brushes.Black;
            tb.GotFocus -= TextBox_GotFocus;
        }

        private void TextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            TextBox box = (TextBox)sender;
             box.Text = "Search Projects";
             box.Foreground = Brushes.LightGray;
             box.GotFocus += TextBox_GotFocus;  
        }
    }
}

Base View Model

public class ViewModelBase : INotifyPropertyChanged
{
    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string projectName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(projectName));
        }
    }
    #endregion INotifyPropertyChanged
}

And the View Model I am using

class MainViewModel : ViewModelBase
{
    private ObservableCollection<Project> projects;
    public ObservableCollection<Project> Projects
    {
        get { return projects; }
        set
        {
            if (value != projects)
            {
                projects = value;
                OnPropertyChanged("Projects");
            }
        }
    }

EDIT:

My ListView constantly changes - as in, the data shown will be based on a User's selection in a TreeView. Right now, my filter doesn't take into consideration what is actually on the screen. It is only filtering based on the binded Collection.

So if my Collection is Fruit, Vegetables, Meat, Dairy and my ListView is Apple, Carrot, and Milk, if I search Apple, the ListView will be blank but if I search Fruit, I will have a populated row. Not correcty, clearly.

Sounds like I am fairly close.

This is my View Model:

public ICollectionView SourceCollection
    {
        get
        {
            return this.projectCollection.View;
        }
    }

    public string FilterText
    {
        get
        {
            return filterText;
        }
        set
        {
            filterText = value;
            this.projectCollection.View.Refresh();
            RaisePropertyChanged("SearchProjectsText");
        }
    }

    private void projectCollection_Filter(object sender, FilterEventArgs e)
    {
        if (string.IsNullOrEmpty(FilterText))
        {
            e.Accepted = true;
            return;
        }

        Project project = e.Item as Project;
        if (project.Name.ToUpper().Contains(FilterText.ToUpper()) || project.Path.ToUpper().Contains(FilterText.ToUpper()))
        {
            e.Accepted = true;
        }
        else
        {
            e.Accepted = false;
        }
    }

    public void RaisePropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Where SearchProjectText is the actual TextBox used for filtering. My relevant XAML:

<TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
        Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="Black" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268"/>
    <TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226" 
              ItemsSource="{Binding Path=Projects}">

<ListView x:Name="ProjectListView" Margin="0,0,10,0" ItemsSource="{Binding SourceCollection}" FontSize="16" Foreground="Black">
2
You're newing up the MainViewModel but viewModel.Projects may still be null?AJ X.

2 Answers

1
votes

You never set the datacontext. Don't both bind and assign; you don't need both. Just set the DataContext of the parent, and bind the appropriate parent property in the XAML (which you already did -- your XAML looks fine):

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }

And make sure your viewmodel properly raises INotifyPropertyChanged.PropertyChanged when you assign a new collection instance to its Projects property.

And make sure you do give Projects a collection, and that there's something in it.

You're using ObservableCollection<T> I hope, not List<T>, for the collection?

Update

I don't know why (it's late and I'm tired, sorry!), but FilterListView is getting called before InitializeComponent(). I should've thought of that when you described the symptoms. This fixes it for me:

private void FilterListView(object sender, TextChangedEventArgs e)
{
    if (this.ProjectListView != null)
    {
        CollectionViewSource.GetDefaultView(this.ProjectListView.ItemsSource).Refresh();
    }
}
0
votes

set the DataContext that will create a new instance of the ViewModel it's binding to.

public MainWindow()
{
   InitializeComponent();
   DataContext = new MainViewModel();
 }