0
votes

So my situation is this: I want to be able to use MVVM with my WPF application using MongoDB. I am very new to MVVM (I know very little of it), but I've got some experience using .NET and WPF.

I have a namespace for recalling MongoDB collections, with the Model component stored there as a class called "User"

Model (in a separate namespace):

public class User
{
    [BsonElement("_id")]
    public ObjectId Id { get; set; }
    public string name { get; set; }
    // other methods listed here

    public async static Task<List<User>> getUserList()
    {
        // allows me to get a list of users
        var col = MongoDBServer<User>.openMongoDB("Users");
        var filter = Builders<User>.Filter.Exists("name");

        List<User> userList = await col.Find(filter).ToListAsync();

        return userList;
    }
}

I've created a very basic ViewModelBase (abstract ViewModelBase):

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if(handler == null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}

And a derived class for handling the User Lists (ViewModel):

public class UserListViewModel : ViewModelBase
{
    private User _user;
    private ObservableCollection<User> _userList;

    public User user
    {
        get { return _user; }
        set
        {
            _user = value;
            OnPropertyChanged("user");
        }
    }

    public ObservableCollection<User> userList
    {
        get { return _userList; }
        set
        {
            _userList = value;
            OnPropertyChanged("userList");
        }
    }

    public UserListViewModel()
    {
        user = new User();
        this.userList = new ObservableCollection<User>();

        // since MongoDB operations are asyncrhonous, the async method "getUserList()" is used to fill the observable collection
        getUserList().Wait();
    }

    public async Task getUserList()
    {
        var UserListRaw = await User.getUserList();
        this.userList = new ObservableCollection<User>(UserListRaw);
    }
}

The view component is as a simple window with a listbox, as follows (View):

<Window x:Class="UserManagementMVVM.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:UserManagementMVVM"
    mc:Ignorable="d"
    Title="UsersWindow" Height="300" Width="300">
    <Window.Resources>
        <local:UserListViewModel x:Key="ViewModel"/>
        <!-- Receiving error for this XAML block saying "Object reference not set to instance of an object -->
    </Window.Resources>

    <Grid DataContext="{Binding ViewModel}">
        <ListBox Margin="5" ItemsSource="{Binding userList}"/>
    </Grid>
</Window>

The App.Xaml and its codebehind are left untouched, as is the View's codebehind.

When I run the program, nothing shows up (ie: The Window starts, but the ListBox is empty even though there is data). I will soon add some button functionality that will perform atomic operations with MongoDB.

I've been trying for nearly 2 weeks to make my own MVVM program for this, with no success. Any assistance would be greatly appreciated.

3
does your userList contain data?gilmishal
Yes. It is a list of "Users" that I call on from MongoDB that has data. It is filled with the "getUserList()" function in the ViewModel.The Don
Did you try ... debugging? "Nothing shows up" isn't really enough information to go by.user1228
yeah, I mean did you see it filled in the debugger?gilmishal
Will -- where should I start debugging from? By "nothing shows up" i mean that the ListBox is empty when the program starts. Apologies for not elaborating.The Don

3 Answers

1
votes

You are not putting the getUserList() return value into a variable

I assume you mean to do the following

Task.Run(async ()=>this.userList = await getUserList());

this shall work you should think wether you want to wait for the task to finish or not, and than place a .Wait() after it.

Your other issue might be the way you bind to the ViewModel in the context it should use StaticResource instead of binding

like This:

<Grid DataContext="{StaticResource ViewModel}">
1
votes
<Window x:Class="UserManagementMVVM.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:UserManagementMVVM"
mc:Ignorable="d"
Title="UsersWindow" Height="300" Width="300">
<Window.DataContext>
    <!--You have to set the DataContext -->
    <local:UserListViewModel x:Key="ViewModel"/>
</Window.DataContext>

<Grid>
    <ListBox Margin="5" ItemsSource="{Binding userList}"/>
</Grid>
</Window>

You have to set the DataContext right. i changed your xaml. but i prefer setting the DataContext for the Mainwindow in Codebehind or app.xaml.cs.

eg: app.xaml.cs

 protected override void OnStartup(StartupEventArgs e)
 {
      var data = new MainWindowViewmodel();
      this.MainWindow = new MainWindow(data);
      this.MainWindow.Show();
 }

all other DataContext for my views are done with DataTemplates within the ResourceDictionary

 <DataTemplate DataType="{x:Type local:MyOtherViewmodel}">
    <local::MyOtherViewmodelView />
 </DataTemplate>
0
votes

I want to give credit to both gilMishal and blindmeis for pointing me in the right direction. Both of your answers have helped. Here is my updated (and functional!) code:

App.xaml.cs has been modified as follows (Credit to blindmeis):

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        UsersWindow window = new UsersWindow();
        var ViewModel = new UserListViewModel();
        window.DataContext = ViewModel;
        window.Show();
    }
}

The ViewModel has been updated:

public class UserListViewModel : ViewModelBase
{
    private User _user;
    private ObservableCollection<string> _userList; // changed from "User" class to string

    public User user
    {
        get { return _user; }
        set
        {
            _user = value;
            OnPropertyChanged("user");
        }
    }

    public ObservableCollection<string> userList
    {
        get { return _userList; }
        set
        {
            _userList = value;
            OnPropertyChanged("userList");
        }
    }

    public UserListViewModel()
    {
        userList = new ObservableCollection<string>();
        Task.Run(async () => this.userList = await getUserList()); // Credit to gilMishal
    }

    public async Task<ObservableCollection<string>> getUserList()
    {
        var UserListRaw = await User.getUserList();
        var userListOC = new ObservableCollection<string>();

        foreach (var doc in UserListRaw) // extracting the "name" property from each "User" object
        {
            userListOC.Add(doc.name);
        }

        return userListOC;
    }
}

And the view:

<Window x:Class="UserManagementMVVM.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:UserManagementMVVM"
        mc:Ignorable="d"
        Title="UsersWindow" Height="300" Width="300">
    <Window.Resources>
        <local:UserListViewModel x:Key="ViewModel"/>
    </Window.Resources>

    <Grid> <!-- data context removed from here, credit blindmeis -->
        <ListBox Margin="5" ItemsSource="{Binding userList}"/>
    </Grid>
</Window>