santosh singh
santosh singh

Reputation: 28652

WPF UI is not updating

I have developed a simple wpf application using MVVM model and code as below.I debug the code and found that collection is updating but UI is not updating even I don't see any record in listbox.

EDIT

enter image description here

<Window x:Class="Model.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WPF Dispatcher Demo" Height="350" Width="525">


    <DockPanel>
        <Grid DockPanel.Dock="Bottom">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="2*" Name="col0" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBox Name="textBlock1" Text="{Binding ShowPath}" 
                 VerticalAlignment="Center" HorizontalAlignment="Left" 
                  Margin="30" Grid.Column="0" />
            <Button Name="FindButton" Content="Select Path" 
              Width="100" Margin="20" Click="FindButton_Click" Grid.Column="1" />
        </Grid>
        <ListBox Name="listBox1"  ItemsSource="{Binding Path=Files}"/>


    </DockPanel>
</Window>

Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Threading;
using System.ComponentModel;
using System.Collections.ObjectModel;


namespace Model
{
    public class DirectorySearchModel : INotifyPropertyChanged
    {
        private ObservableCollection<string> _files = new ObservableCollection<string>();
        private string _showPath;
        public DirectorySearchModel() { }
        void ShowCurrentPath(string path)
        {
            ShowPath = path;
        }


        void AddFileToCollection(string file)
        {
            _files.Add(file);
        }
        public void Search(string path, string pattern)
        {

            if (System.Windows.Application.Current.Dispatcher.CheckAccess())
                ShowPath = path;
            else
                System.Windows.Application.Current.Dispatcher.Invoke(
                  new Action<string>(ShowCurrentPath), DispatcherPriority.Background, new string[] { path }
                );
            string[] files = Directory.GetFiles(path, pattern);
            foreach (string file in files)
            {
                if (System.Windows.Application.Current.Dispatcher.CheckAccess())
                    Files.Add(file);
                else
                    System.Windows.Application.Current.Dispatcher.Invoke(new Action<string>(AddFileToCollection), DispatcherPriority.Background,
                      new string[] { file }
                    );
            }
            string[] dirs = System.IO.Directory.GetDirectories(path);
            foreach (string dir in dirs)
                Search(dir, pattern);
        }

        public string ShowPath
        {
            get
            {
                return _showPath;
            }
            set
            {
                _showPath = value;
                OnPropertyChanged("ShowPath");
            }
        }
        public ObservableCollection<string> Files
        {
            get
            {
                return _files;
            }
            set
            {
                _files = value;
                OnPropertyChanged("Files");
            }
        }
        public void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));

            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}

MainWindow.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Forms;
using System.IO;

namespace Model
{
    public partial class MainWindow : Window
    {
        IAsyncResult cbResult;
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);

        }
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = new DirectorySearchModel().Files;
        }

        private void FindButton_Click(object sender, RoutedEventArgs e)
        {

            FolderBrowserDialog dlg = new FolderBrowserDialog();
            string path = AppDomain.CurrentDomain.BaseDirectory;
            dlg.SelectedPath = path;
            DialogResult result = dlg.ShowDialog();
            if (result == System.Windows.Forms.DialogResult.OK)
            {
                path = dlg.SelectedPath;
                string pattern = "*.*";
                new Model.DirectorySearchModel().Search(path, pattern);
                Action<string, string> proc = new Model.DirectorySearchModel().Search;
                cbResult = proc.BeginInvoke(path, pattern, null, null);

            }
        }
    }
}

Upvotes: 2

Views: 12307

Answers (5)

Silvermind
Silvermind

Reputation: 5944

** Edit **

I was staring blind :)

You're not reusing your model: This works, I just tested it

    private DirectorySearchModel model = new DirectorySearchModel();

    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(MainWindow_Loaded);

    }
    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        this.DataContext = model; //The model is already available as private member
    }

    private void FindButton_Click(object sender, RoutedEventArgs e)
    {

        FolderBrowserDialog dlg = new FolderBrowserDialog();
        string path = AppDomain.CurrentDomain.BaseDirectory;
        dlg.SelectedPath = path;
        DialogResult result = dlg.ShowDialog();
        if (result == System.Windows.Forms.DialogResult.OK)
        {
            path = dlg.SelectedPath;
            string pattern = "*.*";
            Action<string, string> proc = model.Search; // use existing model (the private member).
            cbResult = proc.BeginInvoke(path, pattern, null, null);

        }
    }

In Xaml put this:

<ListBox ItemsSource="{Binding Path=Files}"/>

Upvotes: 2

Rachel
Rachel

Reputation: 132548

In WPF, there are two layers: the UI layer and the Data Layer

The Data Layer is your DataContext, and when you write a normal binding, you are binding to the DataContext.

When you write

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = new DirectorySearchModel().Files;
}

you are telling WPF that the data layer for the form is going to be an ObservableCollection of strings, however ObservableCollection<string> does not have a property called Files, so your ListBox binding is failing.

<!-- Trying to bind to DirectorySearchModel.Files.Files -->
<ListBox ItemsSource="{Binding Path=Files}"/>

You need to change your data layer to your DirectorySearchModel instead, so the binding correctly evaluates to DirectorySearchModel.Files

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = new DirectorySearchModel();
}

But this isn't your only problem. Your Button's Click event operates on a new instance of DirectorySearchModel instead of the existing one.

You could simply use (DirectorySearchModel)MainWindow.DataContext, but this isn't ideal because it tightly combines your UI and your data layers closely together, and it makes the assumption that the DataContext will always be of type DirectorySearchModel.

You could store the DirectorySearchModel used for the DataContext somewhere like moncadad suggested so you can access it from somewhere else in your code:

DirectorySearchModel _model = new DirectorySearchModel();
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    _model = new DirectorySearchModel();
    this.DataContext = _model ;
}

private void FindButton_Click(object sender, RoutedEventArgs e)
{
    // use _model instead of "new DirectorySearchModel()" here
}

But honestly this still isn't ideal because your View and your Data layers are still tightly coupled together, and this isn't compliant with the MVVM design pattern (which you've tagged your question with, so I'm assuming you're using).

The best solution is to actually replace your button's Click event with an ICommand on the DirectorySearchModel so you don't have to worry about storing and accessing a copy of your data layer from your UI layer. This also has the added benefit of keeping application logic inside the application layer, instead of mixing it in with the UI layer:

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = new DirectorySearchModel();
}

<DockPanel>
    <Grid DockPanel.Dock="Bottom">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*" Name="col0" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBox Text="{Binding ShowPath}" ... />

        <Button Content="Select Path" Command="FindButtonCommand" ... />
    </Grid>
    <ListBox ItemsSource="{Binding Path=Files}"/>
</DockPanel>

This correctly separates your UI from your Data layer, which is the primary goal of the MVVM design pattern.

This way, your application logic all stays inside your application objects, while your UI becomes just a pretty user-friendly interface that is used to interact with your application objects.

I like to write beginner WPF articles, and I'd suggest reading my post What is this "DataContext" you speak of? to understand what the DataContext is and how it works a bit better :)

Upvotes: 6

Allan Chua
Allan Chua

Reputation: 10175

You have to set the DataContext of your window to a new instance of DirectorySearchModel. it is not updating because your ListBox inherited the DataContext of the window which is set to the Files property. Your listbox is looking for the Files Property of the Files Collection which does not exists.

You can use this Code

public MainWindow()
{
   this.DataContext = new DirectorySearchModel();
}

this way your list box will have a datacontext of a new directory search model and will be looking for the Files property which you had specify on the Path property of the ItemSource Binding.

UPDATE:

    private void FindButton_Click(object sender, RoutedEventArgs e)
    {

        FolderBrowserDialog dlg = new FolderBrowserDialog();
        string path = AppDomain.CurrentDomain.BaseDirectory;
        dlg.SelectedPath = path;
        DialogResult result = dlg.ShowDialog();
        if (result == System.Windows.Forms.DialogResult.OK)
        {
            path = dlg.SelectedPath;
            string pattern = "*.*";
            //OLD CODE                
            //new Model.DirectorySearchModel().Search(path, pattern);
            //SUGGESTION
            (this.DataContext AS DirectorySearchModel).Search(path, pattern);
            Action<string, string> proc = (this.DataContext AS DirectorySearchModel).Search;
            cbResult = proc.BeginInvoke(path, pattern, null, null);

        }
    }

Upvotes: 1

NTyler
NTyler

Reputation: 1427

You have: this.DataContext = new DirectorySearchModel().Files;

and this: <ListBox Name="listBox1" ItemsSource="{Binding Path=Files}"/>

Which means it will try to bind to DirectorySearchModel().Files.Files. You probably want to change to this: this.DataContext = new DirectorySearchModel();

Upvotes: 2

d.moncada
d.moncada

Reputation: 17402

In your MainWindow.cs, change to:

public partial class MainWindow : Window
{
    IAsyncResult cbResult;
    DirectorySearchModel _model = new DirectorySearchModel();
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = _model;
    }
...

In your Xaml, update your binding to:

<ListBox Name="listBox1"  ItemsSource="{Binding Files, UpdateSourceTrigger="PropertyChanged"}"/>

Upvotes: 1

Related Questions