简体   繁体   中英

WPF ListBox data binding

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.

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.

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.

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.... 屏幕截图

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 ....

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM