Question

Explanation

I'm creating my own search control in WPF. This control is a UserControl that contains an area with search parameters (eg.: search on specific ID, name,...) and a GridView that shows the result.

In my control I have a dependency property of type ICommand where I bind the command to execute my search query.

public static readonly DependencyProperty SearchCommandProperty =
            DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));

Use of my control in a certain window:

 <customControls:SearchControl SearchCommand="{Binding SearchItemsCommand}"
                           SearchResult="{Binding SearchResult}" />
  • SearchItemsCommand is a Command in my ViewModel where I can find my search query. In this command you can find my query to retrieve the result.
  • SearchResult is my ICollection that contains the result of the search query.

Code of the commands

Viewmodel

 private DelegateCommand searchItemsCommand;
        public DelegateCommand SearchItemsCommand
        {
            get
            {
                if (this.searchItemsCommand== null)
                    this.searchItemsCommand= new DelegateCommand(this.SearchItemsCommandExecuted);

                return this.searchItemsCommand;
            }
        }

private ICollection<VoucherOverviewModel> voucherResults;
private void SearchItemsCommandExecuted()
        {
            using (DbContext context = new DbContext())
            {
                var query = (from v in context.Vouchers
                             join vt in context.VoucherTransactions on new
                                                                       {
                                                                           voucherID = v.VoucherID,
                                                                           type = VoucherTransactionType.Out
                                                                       } equals new
                                                                               {
                                                                                   voucherID = vt.VoucherID,
                                                                                   type = vt.Type
                                                                               }
                             join vtype in context.VoucherTypes on v.VoucherTypeID equals vtype.VoucherTypeID
                             join c in context.Customers on vt.CustomerID equals c.CustomerID
                             join pos in context.PointOfSales on v.PointOfSaleID equals pos.PointOfSaleID
                             select new VoucherOverviewModel()
                                    {
                                        PointOfSaleID = v.PointOfSaleID,
                                        PointOfSaleName = pos.Name,
                                        VoucherID = v.VoucherID,
                                        VoucherCode = v.Code,
                                        VoucherTypeID = v.VoucherTypeID,
                                        VoucherTypeDescription = vtype.Code,
                                        CustomerID = c.CustomerID,
                                        CustomerName = c.Name,
                                        Value = vt.Value,
                                        UsedValue = context.VoucherTransactions
                                                           .Where(x => x.VoucherID == v.VoucherID &&
                                                               x.Type == VoucherTransactionType.In)
                                                           .Sum(x => x.Value),
                                        CreateDate = vt.Date,
                                        ValidFrom = v.ValidFrom,
                                        ValidUntil = v.ValidUntil,
                                        ParentVoucherID = v.ParentVoucherID,
                                        Comment = v.Comment,
                                    });

                foreach (ISearchParameter searchParameter in this.SearchParameters)
                {
                    if (!searchParameter.Value.IsNullOrDefault())
                    {
                        switch ((FilterVoucherParameterKey)searchParameter.Key)
                        {
                            case FilterVoucherParameterKey.CustomerID:
                                query = query.Where(x => x.CustomerID == (int)searchParameter.Value);
                                break;
                            case FilterVoucherParameterKey.VoucherID:
                                query = query.Where(x => x.VoucherCode.Contains((string)searchParameter.Value));
                                break;
                            case FilterVoucherParameterKey.PointOfSale:
                                query = query.Where(x => x.PointOfSaleID == (byte)searchParameter.Value);
                                break;
                            case FilterVoucherParameterKey.Type:
                                query = query.Where(x => x.VoucherTypeID == (byte)searchParameter.Value);
                                break;
                        }
                    }
                }

            this.voucherResults = query.ToList();
            }
        }

Custom control

    public static readonly DependencyProperty SearchCommandProperty =
        DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));

 public ICommand SearchCommand
        {
            get
            {
                return (ICommand)this.GetValue(SearchCommandProperty);
            }
            set
            {
                this.SetValue(SearchCommandProperty, value);
            }
        }

This is my dependency property so that I can bind the SearchItemsCommand to my Custom control. Then I have another ICommand to execute the binded command and show the loading element in my custom control. This LocalSearchCommand will be executed when you click on a button.

 private DelegateCommand localSearchCommand;
 public DelegateCommand LocalSearchCommand
    {
        get
        {
            if (this.localSearchCommand == null)
                this.localSearchCommand = new DelegateCommand(this.LocalSearchCommandExecuted);

            return this.localSearchCommand;
        }
    }

    private void LocalSearchCommandExecuted()
    {
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
                       {
                           this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
                       })
                       .ContinueWith(t =>
                       {
                           if (t.IsCompleted)
                           {
                               t.Dispose();
                           }
                       });
    }

The problem

I want to show a Loading element when the query is executing to interact with the user. To show this element, I have to set it visible. The problem now is, when I set it visible and want to execute the search command, my whole UI freezes. After the result is fetched from the database and generated in the GridView, then, and only then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.

loadingElement.Visible = true;
Task.Factory.StartNew(() =>
                       {
                           this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
                       })
                       .ContinueWith(t =>
                       {
                           if (t.IsCompleted)
                           {
                               t.Dispose();
                           }
                       });

I have to use the Dispatcher in my Task to execute the SearchCommand, because it is owned by the UI-thread. But because of the use of the Dispatcher class, I have the same problem as before. My loading element is only shown when the query is already executed, because the Dispatcher executes the search command back on the UI-thread. Without the use of the Dispatcher class, it gives me the following error:

The calling thread cannot access this object because a different thread owns it.

I get this error on the line:

return (ICommand)this.GetValue(SearchCommandProperty);

Even with an empty SearchItemsCommandExecuted method, the error occurs.

What I already tried

  • I tried setting the TaskScheduler of the Task to

    TaskScheduler.FromCurrentSynchronizationContext()

  • I used a lot of combinations of BeginInvoke and Invoke.

  • I tried to set the Visibility of the loading element in the Task.

But none of the above did work.

How can I solve my problem, so that the loading element is shown when the query is executing. Did I miss something obvious?

Thanks in advance!

Loetn

Was it helpful?

Solution 3

I solved my problem with the help of this blog.

What I had to do is to edit the getter of my Dependency property SearchCommand so that it uses the Dispatcher.

public ICommand SearchCommand
        {
            get
            {
                return (ICommand)this.Dispatcher.Invoke(
                       System.Windows.Threading.DispatcherPriority.Background,
                       (DispatcherOperationCallback)delegate { return this.GetValue(SearchCommandProperty); },
                       SearchCommandProperty);

               // Instead of this
               // return this.GetValue(SearchCommandProperty);
            }
            set
            {
                this.SetValue(SearchCommandProperty, value);
            }
        }

And this is my Command method:

  private void LocalSearchCommandExecuted()
        {
            this.loadingElement.Visible = true;
            Task.Factory.StartNew(() =>
                           {
                               this.SearchCommand.Execute(null);
                           })
                           .ContinueWith(t =>
                           {
                               if (t.IsCompleted)
                               {
                                   this.Dispatcher.BeginInvoke((Action)(() => this.loadingElement.Visible= false));
                                   t.Dispose();
                               }
                           });
        }

Thanks for all the help!

OTHER TIPS

The problem is that you are creating a new Task with a ThreadPool thread, but using Dispatcher.Invoke, which runs your command on the UI Thread, hence why your UI is freezing.

You need to offload your SearchCommand work to a background thread, then update your UI with a Continuation on the UI Thread (Dont try updating your UI inside SearchCommand):

then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.

loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
    return this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
    MyUIElement = t.Result; // Update your UI here.
}, TaskScheduler.FromCurrentSynchronizationContext());

Edit: Did not catch your binding of the first command to the second. So the following will proably not work. Looking into it...

EDIT 2: I assumed you want to start the background operation from your viewmodel. In the moment i can't think of another way than to make your loadingItem.Visible property a dependency property, move the background operation to your viewmodel, assign a property which is bound to loadingItem.Visible from there and remove the asynchronus stuff from your usercontrol.

You want to start your query on the background thread and assign the result to your ui thread:

    private void LocalSearchCommandExecuted(object obj)
    {
        //can also be your loadingItem. 
        VisibleElement.Visibility = Visibility.Collapsed;
        //get the ui context
        var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
        Task.Factory.StartNew(() =>
        {
            //do your query on the background thread
            LongRunningOperation();
        })
                  //this happens on the ui thread because of the second parameter scheduler 
                               .ContinueWith(t =>
                               {

                                   if (t.IsCompleted)
                                   {
                                       VisibleElement.Visibility = Visibility.Visible;
                                       //assign the result from the LongRunningOperation to your ui list
                                       _list = new List<string>(_tempList);
                                       //if you need to...
                                       RaisePropertyChanged("SearchResults");
                                   }
                               }, scheduler );

    }

    private void LongRunningOperation()
    {
        //assign your result to a temporary collection
        //if you do not do that you will get an exception: An ItemsControl is inconsistent with its items source
        _tempList = new List<string>();
        for (int i = 0; i < 100; i++)
        {
            _tempList.Add("Test" + i);
            Thread.Sleep(10);
        }
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top