简体   繁体   中英

WPF Datagrid: Force Refresh

I have the following process:

  1. Create a new object in a view/viewmodel and save it in the database via a database handler
  2. Database Handler implements INotifyProperyChanged. So after 1. the database handler notifies other viewmodels about the change. One of the corresponding view contains a datagrid which is bound to a ObservableCollection, which is stored in the database. So the view/viewmodel access the database actively.
  3. The creation of the new object (see 1.) changes the database content and should also updating the datagrid view. So the viewmodel is informed about the change via the notification. So the next step would be to access the database again and filling or renew the observable with these new data.

So how to force the refresh of the datagrid content?

Tried following:

  • Assigning the ObservableCollection temporarly to null does not refresh the datagrid, because it does not notify the datagrid view.
  • Clearing the Collection and adding all items to the new collection (works, but sounds a bit weired because in most cases I will add simply one object to the database)

Here is some sample code:

First the database handler, which handles the data exchange between viewmodels and database. The DBHandler implements the INotifyPropertyChanged to qualify the viewmodels to react on changes in the database. Currently the DBHandler notifies only if the Names List is changed:

public class DBHandler:INotifyPropertyChanged
{
    #region Singleton Pattern

    private static DBHandler instance;

    private DBHandler()
    {
    }

    public static DBHandler GetInstance()
    {
        if (instance == null)
            instance = new DBHandler();
        return instance;
    }

    #endregion

    #region INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion

    // Represents Sample Data of the database
    private List<string> names = new List<string>() { "Sample1", "Sample2" };

    public List<string> Names
    {
        get { return names; }
    }

    /// <summary>
    /// Saves a new name in the database
    /// </summary>
    /// <param name="name"></param>
    public void AddNewName(string name)
    {
        names.Add(name);
        NotifyPropertyChanged();
        }
    }

The MainWindowViewModels can save a new name via the DBHandler, and listens for changes of List DBHandler.Names by using

public class MainWindowViewModel
{
    #region Constructors
    public MainWindowViewModel()
    {
        // Initialize the command for the add button click
        addName = new AddNameCommand();
        // Assign database collection entries
        names = new ObservableCollection<string>(DBHandler.GetInstance().Names);

        DBHandler.GetInstance().PropertyChanged += MainWindowViewModel_PropertyChanged_Names;
    }

    /// <summary>
    /// Listen for the DBHandler.Names change for updating the datagrid view.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void MainWindowViewModel_PropertyChanged_Names(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "Names")
        {
            // Try to update the datagrid view

            // First Try: Reassign
            names = new ObservableCollection<string>(DBHandler.GetInstance().Names);
        }
    }
    #endregion

    private ObservableCollection<string> names;
    public ObservableCollection<string> Names
    {
        get { return names; }
        set { names = value; }
    }

    #region Commands

    /// <summary>
    /// Command for adding the textbox content as new name to the database
    /// </summary>
    public class AddNameCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            Debug.WriteLine("CanExecute");
            return ((string)parameter) != "" || parameter != null;
        }

        public void Execute(object parameter)
        {
            // Save the name in the database
            DBHandler.GetInstance().AddNewName((string)parameter);
        }
    }


    AddNameCommand addName; // Instance of the command which will be intialized in the constructor

    public ICommand btnClickAdd
    {
        get {
            Debug.WriteLine("btnClickAdd");
            return (ICommand) addName; }
    }

    #endregion
}

Last the view contains a TextBox for the name which will be saved by a button click and a DataGrid for displaying all names in the database. So the DataGrid is bounded to the ObservableCollection of Names in the viewmodel.

<Window.Resources>
    <local:MainWindowViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid>
    <DataGrid x:Name="dataGrid" ItemsSource="{Binding Source={StaticResource ViewModel}, Path=Names}" HorizontalAlignment="Left" Margin="48,142,0,0" VerticalAlignment="Top" Height="127" Width="422"/>
    <Button x:Name="button_AddName" Command="{Binding Source={StaticResource ViewModel}, Path=btnClickAdd}" Content="Add" HorizontalAlignment="Left" Margin="331,61,0,0" VerticalAlignment="Top" Width="75" CommandParameter="{Binding Text, ElementName=textBox_Name}"/>
    <TextBox x:Name="textBox_Name" HorizontalAlignment="Left" Height="23" Margin="160,58,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>

</Grid>

使用ICollectionView-将您的Datagrid绑定到它-每当您想刷新Datagrid时在Viewmodel中调用.Refresh()

Delete most of DBHandler , it's just confusing you. All you want to do is retrieve DB stuff when requested, and save it when told. It sits there waiting for orders from MainWindowViewModel . The main viewmodel is always in charge. It uses the model (that's DBHandler ) to store and retrieve information, which it exposes in its properties. It also exposes commands. The view is how the user observes the the viewmodel and talks to it. The viewmodel doesn't know the view exists. All it knows is that somebody out in the darkness occasionally calls the getters and setters on its properties, or calls Execute on one of its commands.

Give MainWindowViewModel a public Names property that's an ObservableCollection<String> . Bind that to the DataGrid or whatever in the UI. Never use List<T> for anything in the viewmodel if there is the slightest chance that you'll be adding or removing items in it.

Write a new Command class called DelegateCommand , something like this:

public class DelegateCommand<T> : ICommand
{
    public DelegateCommand(Action<T> action, Func<T, bool> canExecute = null)
    {
        _action = action;
        _canExecute = canExecute;
    }

    private Action<T> _action;
    private Func<T, bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
        var handler = CanExecuteChanged;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute != null)
        {
            return _canExecute((T)parameter);
        }
        return true;
    }

    public void Execute(object parameter)
    {
        if (_action != null)
        {
            _action((T)parameter);
        }
    }
}

Use it:

public MainWindowViewModel()
{
    // Initialize the command to add a name
    _addNameCommand = new DelegateCommand<string>(DoAddName);

    // Assign database collection entries
    Names = new ObservableCollection<string>(DBHandler.GetInstance().Names);
}

public void DoAddName(String name)
{
    Names.Add(name);
    /*
        Update database here
    */
}

ICommand _addName;

//  Don't name anything "button" in your viewmodel; it's a bad habit to think 
//  that way. It's just a command. If the view wants to use a button to invoke 
//  it, that's the view's business. The viewmodel just makes stuff available. 
public ICommand AddNameCommand
{
    get {
        Debug.WriteLine("getting AddNameCommand");
        return _addNameCommand; 
    }
}
//  Never, never, NEVER NEVER NEVER NEVER touch _names other than in the 
//  get and set blocks of Names. 
//  And make the set private. It should be kept in sync with the database, so 
//  don't let any other class but this one mess with it. 
private ObservableCollection<string> _names = new ObservableCollection<string>();
public ObservableCollection<string> Names
{
    get { return _names; }
    private set { 
        if (_names != value)
        {
            _names = value; 
            NotifyPropertyChanged();
        }
    }
}

I don't know what you're doing to bind Names to the DataGrid , but I infer that it's working OK.

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