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