简体   繁体   中英

Best approach to add an item to a databound Listbox using MVVM pattern?

I'm facing a problem in my WPF project at the moment. At this moment I have a Viewmodel which has a Manager (to communicate with the repo).

internal class TicketViewModel
{
    private TicketManager mgr;

    public IEnumerable<Ticket> List { get; set; }


    public TicketViewModel()
    {
        mgr = new TicketManager();
        List = mgr.GetTickets();

    }

}

I've managed to bind this list to the Listbox in my MainWindow. The next step is that I need to add an extra ticket to the list and also pass this through the manager. The problem is I need two parameters from some Controls in the MainWindow. From MVVM perspective I need to use bound Commands on eg a Button to communicate with the viewmodel as my viewmodel can't/may not access controls from the window. Is using parameterized Commands the way to go here?

The next problem is that the Listbox won't update I guess. This is the code:

<ListBox x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
                <StackPanel Width="250">
                    <TextBlock Text="{Binding TicketNumber}" />
                    <TextBlock Text="{Binding Text}" />
                    <TextBlock Text="{Binding State}" />
            </StackPanel>
            </Border>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

I found that using a CompareableCollection is the way to go here, but then I still have to read all the Tickets again after adding a new Ticket.

Thanks in advance, Hicy

okay here is the code.

Lets say you have three textboxes on MainWindow(since you have three Textblocks.) so Your MainWindow.xaml looks like

<Window.DataContext>
    <local:MyViewModel/>--set's your viewModel
</Window.DataContext>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="250*"/>
        <RowDefinition Height="90"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <ListBox Grid.Row="0" x:Name="listboxTix" BorderThickness="0" ItemsSource="{Binding List}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Border BorderBrush="Bisque" Background="Beige" BorderThickness="2">
                    <StackPanel Width="250">
                        <TextBlock Text="{Binding TicketNumber}" />
                        <TextBlock Text="{Binding Text}" />
                        <TextBlock Text="{Binding State}" />
                    </StackPanel>
                </Border>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <TextBox x:Name="TicketNumber" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=Text}" VerticalAlignment="Top" Width="120"/>
    <TextBox x:Name="Text" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=State}" />
    <TextBox x:Name="State" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=TicketNumber}" />
    <Button Content="Button" Command="{Binding Path=MainCommand}"  Grid.Row="2"/>

</Grid>

and I am assuming that you have some class called class Ticket which contain these three members

Class Ticket
{
    public int TicketNumber { get; set; }
    public string Text { get; set; }
    public string State { get; set; }
}

Now in class TicketManager we fill it with some dummy data

 class TicketManager
{
    ObservableCollection<Ticket> tl = new ObservableCollection<Ticket>();
    internal ObservableCollection<Ticket> GetTickets()
    {
        tl.Add(new Ticket() { State = "State1", Text = "Text1", TicketNumber = 1 });
        tl.Add(new Ticket() { State = "State2", Text = "Text2", TicketNumber = 2 });
        tl.Add(new Ticket() { State = "State3", Text = "Text3", TicketNumber = 3 });

        return tl;
    }

}

and in your Mainwindow ViewModel lets call it MyViewModel.cs we add

class MyViewModel:INotifyPropertyChanged
{
    private TicketManager mgr;
    public ObservableCollection<Ticket> List { get; set; }

    private string text;
    private string state;
    private int ticketNumber;

    private readonly DelegateCommand<object> MyButtonCommand;
    public Class1()
    {
        mgr = new TicketManager();
        List = mgr.GetTickets();
        MyButtonCommand = new DelegateCommand<object>((s) => { AddListToGrid(text, state, ticketNumber); }, (s) => { return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(state); });

    }

    private void AddListToGrid(string text, string state, int ticketNumber)
    {
        List.Add(new Ticket() {Text=text,State=state,TicketNumber=ticketNumber });
    }

    public DelegateCommand<object> MainCommand
    {
        get
        {
            return MyButtonCommand;
        }
    }

    public string Text
    {
        get
        {
            return text;
        }
        set
        {
            text = value;
            OnPropertyChanged("Text");
            MyButtonCommand.RaiseCanExecuteChanged();
        }
    }

    public string State
    {
        get
        {
            return state;
        }
        set
        {
            state = value;
            OnPropertyChanged("State");
            MyButtonCommand.RaiseCanExecuteChanged();
        }
    }

    public int TicketNumber
    {
        get
        {
            return ticketNumber;
        }
        set
        {
            ticketNumber = value;
            OnPropertyChanged("TicketNumber");
            MyButtonCommand.RaiseCanExecuteChanged();
        }
    }

    private void OnPropertyChanged(string p)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(p));
    }


    public event PropertyChangedEventHandler PropertyChanged;
}

You can Modify the code in anyway you want

This ViewModel implements fewthings which are very important from MVVM point of view

1) INotifyPropertyChanged

2) WPF Delegate Command

PS:The code is tested and it runs as expected

Don't get hung up on MVVM it is simply a separation of data from a view, and models are shared between the two with a majority of the business logic (on a shared component) should be performed on the VM; it is not a religion just a three tiered data system. IMHO

If your button needs to do an operation, have it make a call, most likely in the code behind, to a method on the VM which handles the business logic, updates the list with the new item and notifies the manager.

I would bind the list in question to an ObservableCollection which can notify upon insert/delete of an item.

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