Results 1 to 5 of 5

Thread: [RESOLVED] WPF Trying to bind Observable Collection to Listview.

  1. #1

    Thread Starter
    Lively Member
    Join Date
    Mar 2016
    Posts
    106

    Resolved [RESOLVED] WPF Trying to bind Observable Collection to Listview.

    Good Afternoon,

    I have a project that launches from command line. If the command line argument specifies it, then my project will launch with a list view representing a directory browse of the requested folder.

    I started out binding my listview control to a "DirectoryItems" class with two properties one for an Icon to represent a file or folder and the other representing the name of the folder or file. I bound my listview datacontext to the class itself and in a xaml template, I bound each field of that class.

    When I loaded my project, I just had a sub that looped through the objects in the directory, created new listviewitems and added them to the listview. It loaded great. However, now I have added the ability to drill down to a sub directory with a double click...aaaannnndddd thats where the problems started to begin.

    So now I am trying to bind my listview to an Observable Collection (cause people in blogs say to do it this way) and then load the collection and implement a property changed event so that I can clear my listview properly and load new objects.

    So here goes:

    I have a class called "DirectoryItems" with only two properties (I am probably going to add more later) and a method that I call from another class called "BindingListItems" That has the Observable Collection and a method to load the collection.

    DirectoryItems
    Code:
    Imports System.Collections.ObjectModel
    Public Class DirectoryItems
        Public Property ImageSrc As String
        Public Property DirName As String
    
        Public Sub New(imageSource As String, name As String)
            Me.ImageSrc = ImageSrc
            Me.DirName = DirName
        End Sub
    End Class
    BindingListItems
    Code:
    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    Public Class BindingListItems
    
        Implements INotifyPropertyChanged
        Public Property DirCol As ObservableCollection(Of DirectoryItems)
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        Public Sub New(path As String)
            DirCol = New ObservableCollection(Of DirectoryItems)()
            For Each dirInfo As String In IO.Directory.GetDirectories(path)
    
                DirCol.Add(New DirectoryItems(dirInfo.Substring(dirInfo.LastIndexOf("\") + 1), New Uri("Resources/Folder.ico", UriKind.Relative).ToString))
            Next
            For Each fileInfo As String In IO.Directory.GetFiles(path)
                DirCol.Add(New DirectoryItems(fileInfo.Substring(fileInfo.LastIndexOf("\") + 1), New Uri("Resources/Folder.ico", UriKind.Relative).ToString))
            Next
        End Sub
    
    
    End Class
    So what I was hoping for was that I could use BindingListItems as a single class to bind to then if I wanted to create some other class called "FileItems" or something, I could make it and just add code in BindingListItems and not need to change my XAML Binding. However, if I am binding to the Observable Collection, how the hell do I bind/retrieve the specific properties that I added to the Collection (the icon source and the name of the folder/file) to my listviewitem template with Binding?

    XAML
    Code:
    <ListView x:Name="dirViewer" ItemsSource="{Binding BindingListItems.DirCol}" SelectionChanged="dirViewer_SelectionChanged" MouseDoubleClick="dirViewer_MouseDoubleClick" HorizontalAlignment="Stretch" Height="auto" Grid.Row="1" VerticalAlignment="Stretch" Width="auto">
                <ListView.View>
                    <GridView/>
                </ListView.View>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{Binding ImageSrc}"/>
                            <TextBlock Text="{Binding DirName}"/>
                        </StackPanel>
                        </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    Basically what happens now is my project loads but the listview is blank. When I debug, I can see that the collection is being loaded properly. Any help would be greatly appreciated. If I need to change a bunch of stuff around to make this work, I am totally good with that...I am more just trying to understand how binding works.

    Like I have seen examples where in MainWindow initialization you set the datacontext of the listview in codebehind. Is this mandatory or can I just do the Binding all in XAML? Right like if I have to instantiate my class in code behind initialization, kind of defeats the purpose of binding in XAML doesnt it? or no?

    Thank you for your time.

  2. #2
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: WPF Trying to bind Observable Collection to Listview.

    Yes, you have to set a binding context in your Window in order for binding to work. "Do I do it in the form or do I do it in XAML?" is a sometimes stylistic choice in the WPF community, but for many reasons you usually end up doing it in code-behind.

    "So why bother with XAML at all?" is a very good question almost everyone tends to ask after seeing that. Let's talk in metaphor.

    Think about a car stereo with just a radio. You can tune the station, and you can change the volume. That implies some things about UI, but not a specific UI. Here's some different ways car stereo radios are implemented:
    • Digital displays may show the current station.
    • A needle might show the current station.
    • A knob may be used to change the current station.
    • A set of buttons may be used to change the current station.
    • A knob may be used to change the volume.
    • A set of buttons may be used to change the volume.

    If we design our stereo for it, we might opt to make several different faceplates with different combinations of those choices. Then, someone can buy exactly the faceplate they want, plug that in, and use the stereo. It's the same stereo and works the same way, but we've changed the UI.

    That's how WPF is supposed to work. XAML is the faceplate. ViewModels are the stereo. We have to do a tiny bit of work in the code-behind to connect the two, that's the "plugging in a faceplate" step.

    That makes it a little tough to talk about WPF on forums sometimes, because it takes more than one file to pull together an example. This is a quick example of getting a ListView working. Especially when I was just starting out in WPF, I found it was always easier to start with something quick and dirty then refine it to something more advanced:

    MainViewModel.vb:
    Code:
    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class MainViewModel
        Implements INotifyPropertyChanged
    
        Private _items As ObservableCollection(Of String)
        Public Property Items As ObservableCollection(Of String)
            Get
                Return _items
            End Get
            Private Set(value As ObservableCollection(Of String))
                _items = value
                OnPropertyChanged(New PropertyChangedEventArgs("Items"))
            End Set
        End Property
    
        Public Sub New()
            _items = New ObservableCollection(Of String)()
            _items.Add("One")
            _items.Add("Two")
            _items.Add("Several")
        End Sub
    
        Protected Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
            RaiseEvent PropertyChanged(Me, e)
        End Sub
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    End Class
    MainWindow.xaml
    Code:
    <Window x:Class="MainWindow"
            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:WpfApp7"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <ListView ItemsSource="{Binding Items}"/>
        </Grid>
    </Window>
    (You'll have to edit the xmlns:local namespace.)

    MainWindow.xaml.vb
    Code:
    Class MainWindow
    
        Public Sub New()
            InitializeComponent()
    
            DataContext = New MainViewModel()
        End Sub
    
    End Class
    All bindings are relative to the DataContext, so we need to make sure SOMETHING sets the DataContext to an instance of MainViewModel. If we really, REALLY wanted to adhere to "do nothing in code-behind", we could use this XAML:
    Code:
    <Window x:Class="MainWindow"
            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:WpfApp7"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">    
        <Window.DataContext>
            <local:MainViewModel />
        </Window.DataContext>
        <Grid>
            <ListView ItemsSource="{Binding Items}"/>
        </Grid>
    </Window>
    I think this is needlessly dogmatic. It only works if your ViewModel can be created via a parameterless constructor, and in most modern projects that's impractical. I used to stress about this, but here's a better way to look at it:

    "With no code behind" is a thing people write in MSDN magazine for the same reason they write, "With zero lines of code". It's there to sell copies of software.

    If you learn how to use MVVM, you will write very little code behind, and most of your files will only have the bit of code to set the DataContext in them. However, many MVVM frameworks have infrastructure to do that for you. In those, you tend to never actually use code-behind.

    But as you're learning WPF, if the only way you can get something done is with code-behind, I say do it. Then, when it works, post on the forums and ask how to do it without code-behind. It's easier to explain how to transform working code than it is to explain the example without context.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  3. #3

    Thread Starter
    Lively Member
    Join Date
    Mar 2016
    Posts
    106

    Re: WPF Trying to bind Observable Collection to Listview.

    Quote Originally Posted by Sitten Spynne View Post
    Yes, you have to set a binding context in your Window in order for binding to work. "Do I do it in the form or do I do it in XAML?" is a sometimes stylistic choice in the WPF community, but for many reasons you usually end up doing it in code-behind.

    "So why bother with XAML at all?" is a very good question almost everyone tends to ask after seeing that. Let's talk in metaphor.

    Think about a car stereo with just a radio. You can tune the station, and you can change the volume. That implies some things about UI, but not a specific UI. Here's some different ways car stereo radios are implemented:
    • Digital displays may show the current station.
    • A needle might show the current station.
    • A knob may be used to change the current station.
    • A set of buttons may be used to change the current station.
    • A knob may be used to change the volume.
    • A set of buttons may be used to change the volume.

    If we design our stereo for it, we might opt to make several different faceplates with different combinations of those choices. Then, someone can buy exactly the faceplate they want, plug that in, and use the stereo. It's the same stereo and works the same way, but we've changed the UI.

    That's how WPF is supposed to work. XAML is the faceplate. ViewModels are the stereo. We have to do a tiny bit of work in the code-behind to connect the two, that's the "plugging in a faceplate" step.

    That makes it a little tough to talk about WPF on forums sometimes, because it takes more than one file to pull together an example. This is a quick example of getting a ListView working. Especially when I was just starting out in WPF, I found it was always easier to start with something quick and dirty then refine it to something more advanced:

    MainViewModel.vb:
    Code:
    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class MainViewModel
        Implements INotifyPropertyChanged
    
        Private _items As ObservableCollection(Of String)
        Public Property Items As ObservableCollection(Of String)
            Get
                Return _items
            End Get
            Private Set(value As ObservableCollection(Of String))
                _items = value
                OnPropertyChanged(New PropertyChangedEventArgs("Items"))
            End Set
        End Property
    
        Public Sub New()
            _items = New ObservableCollection(Of String)()
            _items.Add("One")
            _items.Add("Two")
            _items.Add("Several")
        End Sub
    
        Protected Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
            RaiseEvent PropertyChanged(Me, e)
        End Sub
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    End Class
    MainWindow.xaml
    Code:
    <Window x:Class="MainWindow"
            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:WpfApp7"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <ListView ItemsSource="{Binding Items}"/>
        </Grid>
    </Window>
    (You'll have to edit the xmlns:local namespace.)

    MainWindow.xaml.vb
    Code:
    Class MainWindow
    
        Public Sub New()
            InitializeComponent()
    
            DataContext = New MainViewModel()
        End Sub
    
    End Class
    All bindings are relative to the DataContext, so we need to make sure SOMETHING sets the DataContext to an instance of MainViewModel. If we really, REALLY wanted to adhere to "do nothing in code-behind", we could use this XAML:
    Code:
    <Window x:Class="MainWindow"
            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:WpfApp7"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">    
        <Window.DataContext>
            <local:MainViewModel />
        </Window.DataContext>
        <Grid>
            <ListView ItemsSource="{Binding Items}"/>
        </Grid>
    </Window>
    I think this is needlessly dogmatic. It only works if your ViewModel can be created via a parameterless constructor, and in most modern projects that's impractical. I used to stress about this, but here's a better way to look at it:

    "With no code behind" is a thing people write in MSDN magazine for the same reason they write, "With zero lines of code". It's there to sell copies of software.

    If you learn how to use MVVM, you will write very little code behind, and most of your files will only have the bit of code to set the DataContext in them. However, many MVVM frameworks have infrastructure to do that for you. In those, you tend to never actually use code-behind.

    But as you're learning WPF, if the only way you can get something done is with code-behind, I say do it. Then, when it works, post on the forums and ask how to do it without code-behind. It's easier to explain how to transform working code than it is to explain the example without context.

    Ahh thanks so much for that analogy! That makes total sense!

    I am reworking my code based on your example and the binding is working...well sort of but thats my fault as I think I now am understanding something else that I am doing wrong.
    So I want to display two things with each listing in my list view...a folder/file icon and the name of the directory/file.

    I was shoving those two properties into my class object and then shoving an instance of that class object into my observable collection...and my listview showed me exactly what I put in there "ESTClient.DirectoryItems" repeated over and over.

    I am guessing that if I want to display two different things in each listviewitem...I should have two different observable collections? One for each item? I am going to try that.

    Thanks so much for your help again...you have helped me with 99% of the questions I have had on here. You must spend alot of your time leading people like me to water...and I appreciate it.

  4. #4
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: WPF Trying to bind Observable Collection to Listview.

    I was shoving those two properties into my class object and then shoving an instance of that class object into my observable collection...and my listview showed me exactly what I put in there "ESTClient.DirectoryItems" repeated over and over.

    I am guessing that if I want to display two different things in each listviewitem...I should have two different observable collections? One for each item? I am going to try that.
    Actually what you were doing was right, but that it wasn't working may indicate something was off about your configuration.

    Let's expand our example and make a 2-property class:

    MainViewModel.vb (Note I added a class at the bottom.)
    Code:
    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class MainViewModel
        Implements INotifyPropertyChanged
    
        Private _items As ObservableCollection(Of ItemViewModel)
        Public Property Items As ObservableCollection(Of ItemViewModel)
            Get
                Return _items
            End Get
            Private Set(value As ObservableCollection(Of ItemViewModel))
                _items = value
                OnPropertyChanged(New PropertyChangedEventArgs("Items"))
            End Set
        End Property
    
        Public Sub New()
            _items = New ObservableCollection(Of ItemViewModel)()
            _items.Add(New ItemViewModel() With {.Text = "Red", .Color = Colors.Red})
            _items.Add(New ItemViewModel() With {.Text = "Green", .Color = Colors.Green})
            _items.Add(New ItemViewModel() With {.Text = "Blue", .Color = Colors.Blue})
        End Sub
    
        Protected Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
            RaiseEvent PropertyChanged(Me, e)
        End Sub
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    End Class
    
    Public Class ItemViewModel
    
        ' Technically should do INotifyPropertyChanged but in this example
        ' it isn't relevant.
        Public Property Text As String
        Public Property Color As Color
    
    End Class
    If you make JUST that change, and run as-is, you'll get like you describe: just a bunch of type names in the ListView. This is a neat strength of WPF but I don't want to bog down the example. The problem is the default ListView template just displays the item as "content". So for Images it actually does the right thing, and for Strings it does the right thing, but for a lot of other things it doesn't know what to do. We need to make an appropriate DataTemplate.

    MainWindow.xaml
    Code:
    <Window x:Class="MainWindow"
            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:WpfApp7"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">    
        <Window.DataContext>
            <local:MainViewModel />
        </Window.DataContext>
        <Grid>
            <ListView ItemsSource="{Binding Items}">
                <ListView.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:ItemViewModel}">
                        <StackPanel Orientation="Horizontal">
                            <Rectangle MinHeight="32" MinWidth="32">
                                <Rectangle.Fill>
                                    <SolidColorBrush Color="{Binding Color}" />
                                </Rectangle.Fill>
                            </Rectangle>
                            <Label Content="{Binding Text}" />
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </Window>
    That's one step away from trying an image, but it seems to work to me:
    Code:
    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class MainViewModel
        Implements INotifyPropertyChanged
    
        Private _items As ObservableCollection(Of ItemViewModel)
        Public Property Items As ObservableCollection(Of ItemViewModel)
            Get
                Return _items
            End Get
            Private Set(value As ObservableCollection(Of ItemViewModel))
                _items = value
                OnPropertyChanged(New PropertyChangedEventArgs("Items"))
            End Set
        End Property
    
        Public Sub New()
            _items = New ObservableCollection(Of ItemViewModel)()
            _items.Add(New ItemViewModel() With {.Text = "Red", .Color = Colors.Red})
            _items.Add(New ItemViewModel() With {.Text = "Green", .Color = Colors.Green})
            _items.Add(New ItemViewModel() With {.Text = "Blue", .Color = Colors.Blue})
        End Sub
    
        Protected Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
            RaiseEvent PropertyChanged(Me, e)
        End Sub
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    End Class
    
    Public Class ItemViewModel
    
        ' Technically should do INotifyPropertyChanged but in this example
        ' it isn't relevant.
        Public Property Text As String
        Public Property Color As Color
    
        Public Property Image As String = "http://placecage.com/100/100"
    
    End Class
    Code:
    <Window x:Class="MainWindow"
            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:WpfApp7"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">    
        <Window.DataContext>
            <local:MainViewModel />
        </Window.DataContext>
        <Grid>
            <ListView ItemsSource="{Binding Items}">
                <ListView.ItemTemplate>
                    <DataTemplate >
                        <StackPanel Orientation="Horizontal">
                            <!--<Rectangle MinHeight="32" MinWidth="32">
                                <Rectangle.Fill>
                                    <SolidColorBrush Color="{Binding Color}" />
                                </Rectangle.Fill>
                            </Rectangle>-->
                            <Image Source="{Binding Image}" />
                            <Label Content="{Binding Text}" />
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </Window>
    So compare what I did to what you have, there's bound to be a difference.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  5. #5

    Thread Starter
    Lively Member
    Join Date
    Mar 2016
    Posts
    106

    Re: WPF Trying to bind Observable Collection to Listview.

    Awesome! That got it working! Thanks again, I learned a TON!

    Also, I have never used the With command before. It allows you to NEW your class without having a constructor. Thats pretty cool!

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width