1

New to WPF here. The application being built has a list of users being pulled from a database for display in a "Users" Window, navigable from a "Main" Window. The list seems to be transferred to the code behind, but the list of users isn't displaying in the "Users" Window ListBox. Does anyone see why this isn't displaying? Many thanks in advance!

"Main" Window directing:

UsersViewModel Usersvm = new UsersViewModel();
Usersvm.Users = new List<UserViewModel>();
DbEntities db = new DbEntities();
var pulledUsers = db.uspGetUsers().ToList();
foreach (var result in pulledUsers)
{
    var pulledUser = new UserViewModel
    {
        FirstName = result.FirstName,
        LastName = result.LastName,
        EMail = result.Email,
        UserID = result.UserID,
        Position = result.Position,
        EndDate = result.EndDate,
    };
    Usersvm.Users.Add(pulledUser);
}
new UsersWindow(Usersvm).Show();

UsersWindow code behind:

public partial class UsersWindow : Window
{
    public UsersWindow(UsersViewModel uvm)
    {
        InitializeComponent();
        listboxUsers.ItemsSource = uvm.Users;
    }
}

UsersWindow.xaml:

<Window x:Class="DbEntities.UsersWindow"
    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:DbEntities"
    mc:Ignorable="d"
    Title="UsersWindow" Height="Auto" Width="900">
    <Window.Resources>
        <Style x:Key="borderBase" TargetType="Border">
            <Setter Property="BorderBrush" Value="Black" />
            <Setter Property="BorderThickness" Value="1" />
        </Style>
    </Window.Resources>
    <StackPanel>
        <TextBlock x:Name="textBlock" Height="21" Margin="0,0,161,0" TextWrapping="Wrap" 
            Text="Users Page" VerticalAlignment="Top" RenderTransformOrigin="1.022,0.409" HorizontalAlignment="Right" Width="344"/>
        <Grid>
            <Grid Grid.IsSharedSizeScope="True">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="151*" />
                        <ColumnDefinition Width="95*" />
                        <ColumnDefinition Width="110*" />
                        <ColumnDefinition Width="351*" />
                        <ColumnDefinition Width="75*" />
                        <ColumnDefinition Width="110*" />
                    </Grid.ColumnDefinitions>
                    <Border Style="{StaticResource borderBase}">
                        <TextBlock HorizontalAlignment="Center" Text="Last Name" />
                    </Border>
                    <Border Grid.Column="1" Style="{StaticResource borderBase}">
                        <TextBlock HorizontalAlignment="Center" Text="First Name" />
                    </Border>
                    <Border Grid.Column="2" Style="{StaticResource borderBase}">
                        <TextBlock HorizontalAlignment="Center" Text="Position" />
                    </Border>
                    <Border Grid.Column="3" Style="{StaticResource borderBase}">
                        <TextBlock HorizontalAlignment="Center" Text="Email" />
                    </Border>
                    <Border Grid.Column="4" Style="{StaticResource borderBase}">
                        <TextBlock HorizontalAlignment="Center" Text="End Date" />
                    </Border>
                    <Border Grid.Column="5" Style="{StaticResource borderBase}">
                        <TextBlock HorizontalAlignment="Center" />
                    </Border>
                    <ListBox x:Name="listboxUsers" HorizontalAlignment="Center" Height="Auto" Margin="3,25,0,0" VerticalAlignment="Top" Width="889"
                    ItemsSource="{Binding Users}" Grid.ColumnSpan="6">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition SharedSizeGroup="LastNameColumn" />
                                    </Grid.ColumnDefinitions>
                                    <Border Style="{StaticResource borderBase}">
                                        <TextBlock Text="{Binding LastName}"/>
                                    </Border>
                                    <Border Style="{StaticResource borderBase}">
                                        <TextBlock Text="{Binding FirstName}"/>
                                    </Border>
                                    <Border Style="{StaticResource borderBase}">
                                        <TextBlock Text="{Binding Position}"/>
                                    </Border>
                                    <Border Style="{StaticResource borderBase}">
                                        <TextBlock Text="{Binding Email}"/>
                                    </Border>
                                    <Border Style="{StaticResource borderBase}">
                                        <TextBlock Text="{Binding EndDate}"/>
                                    </Border>
                                    <Border Style="{StaticResource borderBase}">
                                        <Button Content="Edit" x:Name="editButton" Click="editButton_Click"/>
                                    </Border>
                                </Grid>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </Grid>
            </Grid>
        </Grid>
    </StackPanel>
</Window>

And finally, the UsersViewModel, with a list of the user contact information:

public partial class UsersViewModel : Window
{
    public List<UserViewModel> Users { get; set; }
}

EDIT (Solved): Ed Plunkett's comments and answer directly solved the original ListBox question, and using that input combined with ThyArtIsCode's, which was all neatly presented by Monty, the process is much more elegant. Thanks to all who replied - there's a ton of great learning material here.

jle
  • 269
  • 8
  • 25
  • if you put a breakpoint in this line Usersvm.Users.Add(pulledUser); do you see any values in pulledUser? – Claudius Apr 11 '16 at 17:35
  • 2
    **UsersViewModel : Window** why u inherited window class in UsersViewModel?? – Amol Bavannavar Apr 11 '16 at 17:38
  • 1
    First: `Usersvm.Users` needs to be of type `ObservableCollection`, not `List`. `List` won't notify then UI when its contents change. `ObservableCollection` will. Second, UsersViewModel should not be a window. A window has nothing to do with a viewmodel. Next, it should implement INotifyPropertyChanged and fire `PropertyChanged` when Users is assigned a new collection Next, don't assign to ItemsSource in codebehind. That's not a binding. With the stuff above, the binding for that in your XAML should work correctly. – 15ee8f99-57ff-4f92-890c-b56153 Apr 11 '16 at 17:42
  • But most importantly, an instance of UsersViewModel needs to be the Window's DataContext, in order for the XAML bindings to work. In `public UsersWindow` after Initial InitializeComponent();, just do `var vm = new UsersViewModel();`, initialize it, and then `DataContext = vm;` – 15ee8f99-57ff-4f92-890c-b56153 Apr 11 '16 at 17:44
  • @jle I think there is too much wrong with you code to give simple answer, If this is real code for work, give it back to the original developer (if he still works with you), if this is you own home code you might want to take a look at this https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/ – Monty Apr 11 '16 at 17:49
  • Why are you using ListBox Control for showing Row-Column data structure?.. **You have other option's like ListView, DataGrid etc.** – Amol Bavannavar Apr 11 '16 at 17:56
  • There are so many great responses here that it's taking me a while to go through them: @Claudius - yes, pulledUser is populating correctly – jle Apr 11 '16 at 19:08
  • @Amol Bavannavar - I'm not sure why I had UsersViewModel inheriting Window, but it's been removed. Thanks again for all of the answers and I'll provide an update when I've gone through all of them. – jle Apr 11 '16 at 19:09
  • @EdPlunkett - your 2 comments were the first to solve the problem. If you wouldn't mind creating an answer with those responses, I'd be happy to mark it correct! – jle Apr 11 '16 at 20:19

3 Answers3

3

I see a couple things wrong...

First, your ViewModel is inheriting Window. If there isn't a particular reason for this, get rid of it. If you want to notify UI of changes made to your collection (which should ideally be part of your view model), make the view model inherit INotifyPropertyChanged.

You are also binding to ListBox here:

ItemsSource="{Binding Users}"

AND setting the ItemsSource again here:

listboxUsers.ItemsSource = uvm.Users;

BAD! If you are binding in XAML, there's absolutely no need to set the ItemsSource again. Need to modify the collection? Do so with the collection directly.

Also, since you're new to WPF, I figured I'd add some suggestions that helped me when I first started learning:

  1. If you want things to go quicker, add IsAsync=True to your ListBox binding. This will enable asynchronous binding (amazing, I know).
  2. Virtualize the crap out of that ListBox (simply add following to ListBox):

     VirtualizingPanel.IsVirtualizing="True"                
     VirtualizingPanel.VirtualizationMode="Recycling"
    

And one last thing, though others suggested using an ObservableCollection, it also comes with a performance hit when using large data. Even if you don't intend to have large data, it always safer to use a BindingList anyway. In fact, ObservableCollection has an upper hand when working with smaller data sets.

They are much quicker and share many similar properties as the OC.

  • 1
    It's not safer to use a BindingList. It has more overhead than an ObservableCollection. They were also, intended to be used with WinForms rather than WPF. Here's a good example on how it has more overhead: http://www.biglittleendian.com/article/21 There are also a lot of other SO posts that describe it as well. – d.moncada Apr 11 '16 at 18:32
  • 1
    Thanks for the note. I base my knowledge off this post: http://stackoverflow.com/questions/3305383/wpf-whats-the-most-efficient-fast-way-of-adding-items-to-a-listview, which indicates a BindingList performs better when handling large data sets as opposed to the ObservableCollection (which I agree, is intended for Windows Forms and should be used sparingly in WPF). –  Apr 11 '16 at 18:39
  • 1
    Interesting.. Makes me want to come up with a test, comparing the two. If I do, ill throw it up on CodeProject tonight! – d.moncada Apr 11 '16 at 18:46
  • @ThyArtIsCode - thank you very much for the response. A previous comment came first that answered the question, so I'll mark that one correct, but your answer is great and gives me plenty to learn. – jle Apr 11 '16 at 20:34
  • No worries, glad I could help (: –  Apr 11 '16 at 20:44
2

You've got a few things to fix here, but nothing very complicated. Just a lot of MVVM/XAML housekeeping stuff.

The way MVVM works in XAML is that your viewmodels don't know about your views --- ideally they don't know about any UI at all. To make that happen with stuff like message boxes and file open dialogs can involve some contortions, but we're not going there right now. Incidentally, you definitely don't want to derive a view model from Window -- that's a UI class, and it doesn't do anything that view models need a base class to do.

So your viewmodels have public properties, which you've got, but when those properties change, they should fire notifications off into the darkness. To do that, you implement INotifyPropertyChanged on your viewmodel, and you fire the PropertyChanged event when a property changes. The UI will subscribe to those notifications -- if your view model is the DataContext of the element whose property is bound (clear as mud -- more on that later).

When a viewmodel exposes a collection, it usually uses ObservableCollection, because that class fires notifications on add/remove/etc. List doesn't do that. ObservableCollection comes with some overhead from all the notification stuff, so don't just use it everywhere -- still use List when all you need is a List.

So UsersViewModel.Users needs to be of type ObservableCollection<UserViewModel><UserViewModel>, and when the collection is replaced, fire PropertyChanged.

private ObservableCollection<UserViewModel> _users =
    new ObservableCollection<UserViewModel>();
ObservableCollection<UserViewModel> Users {
    get { return _users; }
    set {
        _users = value;
        //  Implementations of this are everywhere on Google, very simple. 
        OnPropertyChanged("Users");
        //  Or in C#6
        //PropertyChanged?.Invoke(new PropertyChangedEventArgs(nameof(Users)));
    }
}

And of course make sure UserViewModel also implements INotifyPropertyChnaged and fires notifications when its own property values change.

Next, your XAML binding for ItemsSource on the ListBox is correct, but assigning a collection to that property in code behind will break it. A {Binding ...} in XAML isn't just an assignment: It creates an instance of the Binding class, which sits in the middle and manages all the notification event business I mentioned above. You can create bindings programmatically, but doing it in XAML is much simpler and in 99.5+% of cases does everything you need.

Most importantly, the window needs to know about your viewmodel. Make that happen by assigning an instance of UsersViewModel to the window's DataContext. The window's child controls will inherit that DataContext, and all bindings will be evaluated in that context.

public partial class UsersWindow : Window
{
    public UsersWindow(UsersViewModel uvm)
    {
        InitializeComponent();

        var vm = new UsersViewModel();
        //  initialize vm if needed
        DataContext = vm;
    }
}

You could have the window's creator pass in a UsersViewModel instance via the window's constructor as well.

  • 1
    Thanks Ed! This is a wonderful explanation. The last few paragraphs especially helped by solving the immediate problem. I'll now go tackle the first parts of the answer to address the usability. – jle Apr 11 '16 at 20:45
1

OK try this.....

ViewModel....

class Base_ViewModel : INotifyPropertyChanged
{
    public RelayCommand<UserViewModel> editButton_Click_Command { get; set; }

    public Base_ViewModel()
    {
        editButton_Click_Command = new RelayCommand<UserViewModel>(OneditButton_Click_Command);

        this.Users = new ObservableCollection<UserViewModel>();

        this.Users.Add(new UserViewModel() { FirstName = "John", LastName = "Doe", EMail = "JohnDoe@yahoo.com", EndDate = "02-01-2016", Position = "Developer", UserID = "AADD543" });
    }

    private ObservableCollection<UserViewModel> _Users;
    public ObservableCollection<UserViewModel> Users
    {
        get { return _Users; }
        set { _Users = value; NotifyPropertyChanged("Users"); }
    }

    private void OneditButton_Click_Command(UserViewModel obj)
    { // put a break-point here and you will see the data you want to Edit in obj

    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

User Class.....

public class UserViewModel : INotifyPropertyChanged
{
    private string _FirstName;
    public string FirstName
    {
        get { return _FirstName; }
        set { _FirstName = value; NotifyPropertyChanged("FirstName"); }
    }

    private string _LastName;
    public string LastName
    {
        get { return _LastName; }
        set { _LastName = value; NotifyPropertyChanged("LastName"); }
    }

    private string _EMail ;
    public string EMail
    {
        get { return _EMail; }
        set { _EMail = value; NotifyPropertyChanged("EMail"); }
    }

    private string _UserID;
    public string UserID
    {
        get { return _UserID; }
        set { _UserID = value; NotifyPropertyChanged("UserID"); }
    }

    private string _Position;
    public string Position
    {
        get { return _Position; }
        set { _Position = value; NotifyPropertyChanged("Position"); }
    }

    private string _EndDate;
    public string EndDate
    {
        get { return _EndDate; }
        set { _EndDate = value; NotifyPropertyChanged("EndDate"); }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

XAML.....

Set the Window x:Name....

<Window x:Name="Base_V"......

DataContext

<Window.DataContext>
    <ViewModels:Base_ViewModel/>
</Window.DataContext>

And the rest of the View....

<Grid>
    <DataGrid Name="DataGrid1" ItemsSource="{Binding Users}">
        <DataGrid.Columns>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Button Command="{Binding DataContext.editButton_Click_Command, ElementName=Base_V}" CommandParameter="{Binding}">Edit</Button>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>
</Window>

you should end up with something like this.... Screen Shot

Update 1

In the constructor of the Base_ViewModel

        this.Users.Add(new UserViewModel() { FirstName = "John", LastName = "Doe", EMail = "JohnDoe@yahoo.com", EndDate = "02-01-2016", Position = "Developer", UserID = "AADD543" });
        this.Users.Add(new UserViewModel() { FirstName = "Fred", LastName = "Doe", EMail = "FredDoe@yahoo.com", EndDate = "02-01-2016", Position = "Developer", UserID = "AADD543" });
        // empty record to allow the use to Add a new record
        this.Users.Add(new UserViewModel());

When the user selects the Edit button for the empty record they are in effect simply filling in a blank record, Once they have filled that in, make sure to add another blank record to produce a new (empty row) in the DataGrid ....

Monty
  • 1,534
  • 2
  • 10
  • 12
  • Thanks very much for your comment and this answer, Monty! I tried implementing this after the original problem was solved to add a more elegant solution, but came across the errors: namespace or assembly error on the ViewModel's "RelayCommand" lines; and type error in the view's DataContext declaration. I haven't defined an ICommand for "RelayCommand", so might that be the reason for the ViewModel error? Also, what might be the cause of the DataContext not recognizing the ViewModel? – jle Apr 11 '16 at 21:17
  • Yeah you'll have to add xmlns:ViewModels="clr-namespace:DbEntities" and RelayCommand class can be found here... http://www.kellydun.com/wpf-relaycommand-with-parameter/ – Monty Apr 11 '16 at 21:30
  • Monty, This appeared to work at first glance, but whenever a selected cell value is changed in the UI, the original values are being sent to the OneditButtonClick. Is there a way to bind the changed/new parameters in the UserViewModel? This would solve a couple of other questions I'd posted. – jle Apr 13 '16 at 20:56
  • There probably is (if you edit a cell then select another row before pressing the button for the edited row you'll see that the data has changed). That would be perfectly logical as the changed data is only committed once the user selects another row (Manually editing data in an SQL Server table is the same) however, the button is there to allow the user to edit the data in the row, it makes little sense to allow row\cell editing AND have the edit button as well. What you want is to prevent the user from manually editing the value in the cell.... – Monty Apr 13 '16 at 21:20
  • does that quite nicely and leaves the button Enabled.... – Monty Apr 13 '16 at 21:25
  • Ah, thanks for the response. So you suggest the Edit button direct the user to another page where that specific row can be edited? I was just going to allow editing in the grid, then in the OneditButtonClick determine whether the row was new (UserID == 0), and save the new item, or old (UserID > 0) and save the existing edited Item. For the latter way, I'm trying to find a method to send the changed data to the event without having to activate another row. – jle Apr 13 '16 at 21:33
  • The problem with allowing a user to edit data in a 'Grid' is that it gets confusing, they make changes to several different Rows and Cells then think 'Oh.... what have I done' then they look for the 'Undo' button. Is better to ONLY let the user edit one row\record at a time so, yes a separate editing window with 'OK' 'Cancel' buttons. Any changes made to that object will be instantly updated in the Grid (magic of Binding).... – Monty Apr 13 '16 at 21:48
  • Also, to add a new record (coz now the Grid is 'ReadOnly' and the user cannot add a new row) always make sure that there is an empty record in this.Users in the Base_ViewModel…. I'll update my answer with a quick fix for that.. – Monty Apr 13 '16 at 21:48
  • Monty, this works very well. Thanks a bunch. You're a life saver! – jle Apr 14 '16 at 19:39