简体   繁体   中英

MVVM with Entity Framework

I am trying to build a small application with a simple Crud.Basically a listbox with four buttons. I know that i need a Model,ViewModel and a View.But among other question how to bind my context entities to my list box.I am new to wpf and mvvm.

this is my Model :

public class Suplemento
{
    public int Id { get; set; }
    public string Name { get; set; }
}

this is my ViewModel :

public class SuplementoViewModel : ViewModelBase
{
    private Suplemento current = null;
    private int selectedIndex = 0;


    public SuplementoViewModel()
    {
        Suplementos = new ObservableCollection<Suplemento>(MedPlusDatabase.Instance.Suplementos);
    }

    public ObservableCollection<Suplemento> Suplementos { get; set; }
    public bool IsLoaded { get; private set; }
    public ICommand EditCommand { get; private set; }


    public bool CanEdit
    {
        get { return IsLoaded && current != null; }
    }

    public int SelectedIndex
    {
        get { return selectedIndex; }
        set
        {
            if (selectedIndex != value)
            {
                selectedIndex = value;
                RaisePropertyChanged("SelectedIndex");
            }
        }
    }

    public Suplemento Current
    {
        get { return current; }
        set
        {
            if (current != value)
            {
                current = value;
                RaisePropertyChanged("Current");
            }
        }
    }

}

and this is my View :

<Window x:Class="MedPlus.frmSuplemento"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MedPlus"
    Title="Suplementos" 
    Height="470" 
    Width="400" 
    WindowStartupLocation="CenterOwner" 
    ResizeMode="CanResize" 
    WindowStyle="SingleBorderWindow" 
    MinHeight="470" 
    MinWidth="400" 
    Icon="pack://application:,,,/Resources/suplemento.png" 
    ContentRendered="Window_ContentRendered">

<Grid>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="15" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="15" />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="43" />
    </Grid.RowDefinitions>

    <DockPanel Grid.Row="0" Grid.Column="1" Margin="0,10,0,5">
        <TextBlock DockPanel.Dock="Top" Text="Nome do Suplemento" />
        <TextBox DockPanel.Dock="Bottom" />
    </DockPanel>

    <ListBox Name="lbSuplementos" 
             ItemsSource="{Binding Suplementos}"
             DisplayMemberPath="Name"
             SelectedItem="{Binding Current, Mode=TwoWay}"
             SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" 
             Grid.Row="1" Grid.Column="1" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Height="18" Text="{Binding Path=Name}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    <Grid Grid.Row="2" Grid.Column="1" >

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="73" />
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="73" />
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="73" />
            <ColumnDefinition Width="10" />
            <ColumnDefinition Width="73" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Button Name="btnNew" Content="_Novo" Grid.Column="1" IsDefault="True" Click="btnNew_Click" />
        <Button Name="btnEdit" Content="_Editar" Grid.Column="3" />
        <Button Name="btnDelete" Content="E_xcluir" Grid.Column="5" />
        <Button Name="btnCancel" Content="_Cancelar" Grid.Column="7" IsCancel="True" Click="btnCancel_Click" />

    </Grid>      

</Grid>

What should be in my ViewModel constructor ?

To start, you are missing some key concepts behind the MVVM pattern. One of the major goals when implementing the Purist MVVM pattern is the remove all code in the code behind of your XAML Window or User Control. See how on your btnNew Button control you have the btnNew_Click event handler? That breaks the pure MVVM pattern.

You should look into different implementations of a RelayCommand class. Here is one I have used in the past:

public class RelayCommand : ICommand
{

    private readonly Action<object> _execute = null;
    private readonly Predicate<object> _canExecute = null;
    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action<object> execute)
        :this(execute, null) {}


    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged(this, new EventArgs());
    }
}

In your ViewModel, you would create a property called AddNewCommand like so:

public RelayCommand AddNewCommand { get; set; }

Then you define a private method to perform actions when that command is invoked:

private void AddNew(object param)
{
    //Code to Add Something
}

In your constructor you initialize the Command:

public SuplementoViewModel()
{
    this.AddNewCommand = new RelayCommand(AddNew);
}

Then bind your command to your button in the XAML code. Don't forget to add the ViewModel to the DataContext of your Window or UserControl

DataContext Binding

Reference NameSpace First

xmlns:vm="clr-namespace:MyApp.ViewModels"

Bind DataContext to ViewModel

<Window.DataContext>
    <vm:SuplementoViewModel />
</Window.DataContext>

Bind Command To Button

<Button Name="btnNew" Content="_Novo" Grid.Column="1" IsDefault="True" Command="{Binding AddNewCommand}" />

Now that you have bound your ViewModel to your Window / UserControl, in your constructor of your ViewModel, you need to populate your Suplementos ObservableCollection with some data so your listbox has something to show. However, you need to change the definition of your Suplementos property. Here's what is should look like:

private ObservableCollection<Suplemento> _suplementos;

public ObservableCollection<Suplemento> Suplementos
{
    get
    {
        return this._suplementos; 
    }
    set
    {
        this._suplementos = value;
        RaisePropertyChanged("Suplementos");
    }
}

Here's why. Say you kept your's the way it was and you populated your ObservableCollection in the constructor like so:

public SuplementoViewModel()
{
    //Service is whatever method you are using to retrieve your values from the database
    this.Suplementos = Service.GetSumplementos();
}

You will end up with an exception. The reason being is that when your Window / UserControl is instantiated, it fires an InitializeComponent() method call in its constructor. This method instantiates all the content and controls in your view, including your ViewModel. When your ViewModel is instantiated, your constructor runs and populates your ObservableCollection before any of the controls in your Window / UserControl have been instantiated. If your list box is 'Observing' your ObservableCollection, how can it 'Observe' the change you've made (populating your Collection in your constructor), if it doesn't even exist yet?

By refactoring your property to what I described earlier, you're constructor should look like this:

public SuplementoViewModel()
{
    this._suplementos = Service.GetSuplementos();
}

See how I used the private container variable instead of directly populating the Suplementos property? By doing this, the RaisePropertyChanged() method does not get fired, thereby avoids notifying your ListBox which doesn't exist. Once your ListBox is instantiated it will read the property it's bound too, and since you've already populated the private container variable with data, it will have some information to list.

Also note, for you ListBox to get notified of changes to your collection, and to post the selected item back to your view model your XAML should look like this:

<ListBox ItemsSource="{Binding Suplementos, Mode=OneWay, UpdateSourceTrigger=PropertyChanged"
SelectedItem="{Binding Current, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" />

That's all I got for now. Good luck and happy WPFing!

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