Problem with running tasks sequentially using ContinueWith - wpf

I want to start a proccess by checking toggleButton and after finishing proccess toggleButton be Unchecked.
Here is my code.
Proccess.xaml :
<ToggleButton Command="{Binding StartProccessCommand}" Content="Proccessing" IsChecked="{Binding isChecked,Mode=TwoWay}"></ToggleButton>
ProccessViewModel.cs :
public class ProccessViewModel: BindableBase
{
private bool _isChecked = false;
public bool isChecked
{
get { return _isChecked; }
set { SetProperty(ref _isChecked, value); }
}
public DelegateCommand StartProccessCommand{ get; set; }
public ProccessViewModel()
{
StartProccessCommand= new DelegateCommand(OnToggleButtonClicked);
}
public async void OnToggleButtonClicked()
{
await Task.Run(() => {
isChecked= true;
for (int i = 0; i < 50000; i++)
{
Console.WriteLine(i);
}
}).ContinueWith((x) =>
{
for (int i = 50000; i < 100000; i++)
{
Console.WriteLine(i);
}
isChecked= false;
}
}
BUT when I run code ToggleButton Unchecked immediately after checking.
Result :
ToggleButton Checked
ToggleButton Unchecked
1
2
.
.
49999
50000
50001
.
.
100000

Why are you using ContinueWith with await? It makes no sense since the remainder of OnToggleButtonClicked will be executed once the awaited Task has finished.
Set the property, await the first Task and then await another Task and set the property back to false:
public async void OnToggleButtonClicked()
{
isChecked = true;
await Task.Run(() => {
for (int i = 0; i < 50000; i++)
{
Console.WriteLine(i);
}
});
await Task.Run(() =>
{
for (int i = 50000; i < 100000; i++)
{
Console.WriteLine(i);
}
});
isChecked = false;
}

Related

Update ViewModel property from task

I am building an app with WPF and Caliburn.Micro. I want to update a ProgressBar from an Task/Thread and I am wondering what I need to correctly update the UI:
public class DemoViewModel : PropertyChangedBase
{
private int m_Progress;
public int Progress
{
get { return m_Progress; }
set
{
if (value == m_Progress) return;
m_Progress = value;
NotifyOfPropertyChange();
NotifyOfPropertyChange(nameof(CanStart));
}
}
public bool CanStart => Progress == 0 || Progress == 100;
public void Start()
{
Task.Factory.StartNew(example);
}
private void example()
{
for (int i = 0; i < 100; i++)
{
Progress = i + 1; // this triggers PropertChanged-Event and leads to the update of the UI
Thread.Sleep(20);
}
}
}
From other programming languages I know that I need to synchronize with the UI thread to update the UI but my code just works. Is there something I missed and which could cause sporadic errors or is there some magic behind the scenes which care of the synchronization?
It will depend on how you've implemented INotifyPropertyChanged. The implementation should delgate all UI updates to the appropriate dispatcher.
Sample Implementation:
public void RaisePropertyChanged([CallerMemberName]string name) {
Application.Current.Dispatcher.Invoke(() => {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}, System.Windows.Threading.DispatcherPriority.Background);
}
Also to clean up the task start a bit:
Edit:
Removed unnecessary bool return value, and set ConfigureAwait to stay off UI thread when task completes.
public async void Start()
{
await Task.Run(() => example()).ConfigureAwait(false);
}
private async Task example()
{
for (int i = 0; i < 100; i++)
{
Progress = i + 1; // this triggers PropertChanged-Event and leads to the update of the UI
await Task.Delay(20);
}
}

Paging ListBox with ReactiveUI and Caliburn.Micro

I'm trying to implement a paging mechanism for a listbox using Caliburn.Micro.ReactiveUI with a call to EF using ".Skip(currentPage).Take(pageSize)". I'm new to ReactiveUI and Reactive in general. I'm sure this is supposed to be easy.
I've got a single "SearchParameters" class which I needs to be observed and the search function needs to execute when any of the properties on the SearchParameters object changes.
You can see from the commented-out code that I've tried to define the class as a ReactiveObject as well. The current implementation though is with CM's PropertyChangedBase. The individual properties are bound textboxes in my view using CM's conventions:
public class SearchParameters : PropertyChangedBase
{
private string _searchTerm;
public string SearchTerm
{
get { return _searchTerm; }
set
{
if (value == _searchTerm) return;
_searchTerm = value;
NotifyOfPropertyChange(() => SearchTerm);
}
}
private int _pageSize;
public int PageSize
{
get { return _pageSize; }
set
{
if (value == _pageSize) return;
_pageSize = value;
NotifyOfPropertyChange(() => PageSize);
}
}
private int _skipCount;
public int SkipCount
{
get { return _skipCount; }
set
{
if (value == _skipCount) return;
_skipCount = value;
NotifyOfPropertyChange(() => SkipCount);
}
}
//private string _searchTerm;
//public string SearchTerm
//{
// get { return _searchTerm; }
// set { this.RaiseAndSetIfChanged(ref _searchTerm, value); }
//}
//private int _pageSize;
//public int PageSize
//{
// get { return _pageSize; }
// set { this.RaiseAndSetIfChanged(ref _pageSize, value); }
//}
//private int _skipCount;
//public int SkipCount
//{
// get { return _skipCount; }
// set { this.RaiseAndSetIfChanged(ref _skipCount, value); }
//}
}
"SearchService" has the following method which needs to execute when any one of SearchParameter's values change:
public async Task<SearchResult> SearchAsync(SearchParameters searchParameters)
{
return await Task.Run(() =>
{
var query = (from m in _hrEntities.Departments select m);
if (!String.IsNullOrEmpty(searchParameters.SearchTerm))
{
searchParameters.SearchTerm = searchParameters.SearchTerm.Trim();
query = query.Where(
x => x.Employee.LastName.Contains(searchParameters.SearchTerm) || x.Employee.FirstName.Contains(searchParameters.SearchTerm)).Skip(searchParameters.SkipCount).Take(searchParameters.PageSize);
}
return new SearchResult
{
SearchTerm = searchParameters.SearchTerm,
Matches = new BindableCollection<DepartmentViewModel>(query.Select(x => new DepartmentViewModel{ Department = x }).Skip(searchParameters.SkipCount).Take(searchParameters.PageSize))
};
});
}
Here's how I've tried to wire all of this up in MainViewModel's ctor and where Rx gets hazy for me:
public class MainViewModel : ReactiveScreen
{
private SearchParameters _searchParameters;
public SearchParameters SearchParameters
{
get { return _searchParameters; }
set
{
if (value == _searchParameters) return;
_searchParameters = value;
NotifyOfPropertyChange(() => SearchParameters);
}
}
{
public void MainViewModel()
{
var searchService = new SearchService();
//default Skip and PageSize values
SearchParameters = new Services.SearchParameters { SkipCount = 0 , PageSize = 10};
var searchParameters = this.ObservableForProperty(x => x.SearchParameters)
.Value()
.Throttle(TimeSpan.FromSeconds(.3));
var searchResults = searchParameters.SelectMany(parameters => searchService.SearchAsync(parameters));
var latestMatches = searchParameters
.CombineLatest(searchResults,
(searchParameter, searchResult) =>
searchResult.SearchTerm != searchParameter.SearchTerm
? null
: searchResult.Matches)
.Where(matches => matches != null);
_departmentViewModels = latestMatches.ToProperty(this, x => x.DepartmentViewModels);
searchParameters.Subscribe(x => Debug.WriteLine(x));
}
}
In the above example the call to SearchAsync doesn't execute. It seems that changes to SearchParameter's properties aren't being observed.
Can anyone tell me what I'm doing wrong here?
Here's how I ended up doing this although I'd be interested in hearing other solutions if anyone has suggestions. I'm not sure if this is the best way but it works:
First, I defined a computed property in my SearchParameters class that returns a string and reevaluates anytime CurrentPage, SkipCount and PageSize are updated from the View:
public string ParameterString
{
get { return String.Format("SearchTerm={0}|SkipCount={1}|PageSize={2}", SearchTerm, SkipCount, PageSize); }
}
Next, in my MainViewModel ctor I simply observe the computed rather than attempting to react to SearchTerm, SkipCount and PageSize individually (which my original question was asking how to do):
var searchTerms = this
.ObservableForProperty(x => x.SearchParameters.ParameterString)
.Value()
.Throttle(TimeSpan.FromSeconds(.3));
var searchResults = searchTerms.SelectMany(parameters => SearchService.SearchAsync(parameters));
var latestMatches = searchTerms
.CombineLatest(searchResults,
(searchTerm, searchResult) =>
searchResult.SearchTerm != searchTerm
? null
: searchResult.Matches)
.Where(matches => matches != null);
Finally, in my SearchService I parse the parameter string to get the current values:
var parameters = searchParameters.Split('|');
var searchTerm = "";
var skipCount = 0;
var pageSize = 0;
foreach (var parameter in parameters)
{
if (parameter.Contains("SearchTerm="))
{searchTerm = parameter.Replace("SearchTerm=", "");}
else if (parameter.Contains("SkipCount="))
{ skipCount = Convert.ToInt32(parameter.Replace("SkipCount=", "")); }
else if (parameter.Contains("PageSize="))
{ pageSize = Convert.ToInt32(parameter.Replace("PageSize=", "")); }
}

Cross threading exception accessing viewmodel property set from code behind

I have a WPF MVVM app that displays drug information in a telerik grid view. I want to do some filtering and paging in the view model but I keep getting cross-thread exceptions. The initial load works fine. I can change pages/page size without a problem. When I filter the grid, I create an IEnumerable of FilterDescriptors in the code behind and then set it on the Filters property of the view model. At this point, I get the cross-thread exception when doing the query using the filters. I have tried everything I can think of but can't get it to work.
public partial class DrugEdit : Page
{
public DrugEdit()
{
InitializeComponent();
}
private void RadGridView1_Filtered(object sender, Telerik.Windows.Controls.GridView.GridViewFilteredEventArgs e)
{
DrugEditViewModel vm = this.DataContext as DrugEditViewModel;
Telerik.Windows.Controls.RadGridView gv = sender as Telerik.Windows.Controls.RadGridView;
if (gv.FilterDescriptors == null || gv.FilterDescriptors.Count == 0)
vm.Filters = null;
else
{
List<FilterDescriptor> filters = (vm.Filters==null? new List<FilterDescriptor>(): vm.Filters.ToList());
foreach (FilterDescriptor r in e.Removed)
{
var fnd = filters.FirstOrDefault(x => x.Member == r.Member);
if (fnd == null) continue;
filters.Remove(fnd);
}
foreach (FilterDescriptor a in e.Added)
{
var fnd = filters.FirstOrDefault(x => x.Member == a.Member);
if (fnd == null)
{
filters.Add(new FilterDescriptor(a.Member, a.Operator, a.Value, false, a.MemberType));
}
else
{
fnd.Operator = a.Operator;
fnd.Value = a.Value;
}
}
vm.Filters = filters;
}
}
}
public class DrugEditViewModel
: ViewModelBase
{
private Data.DBContext ctx = new Data.DBContext();
private List<FilterDescriptor> FiltersValue = new List<FilterDescriptor>();
public List<FilterDescriptor> Filters
{
get { return FiltersValue; }
set
{
SetPropertyValue((() => Filters), ref FiltersValue, value);
RecordCount = 0;
LoadData();
}
}
private int PageValue = 0;
public int Page
{
get { return PageValue; }
set
{
SetPropertyValue((() => Page), ref PageValue, value);
LoadData();
}
}
private int PageSizeValue = 20;
public int PageSize
{
get { return PageSizeValue; }
set
{
SetPropertyValue((() => PageSize), ref PageSizeValue, value);
Page = 0;
}
}
private int RecordCountValue;
public int RecordCount
{
get { return RecordCountValue; }
set
{
SetPropertyValue((() => RecordCount), ref RecordCountValue, value);
}
}
private ObservableCollection<Models.Drug> DrugsValue;
public ObservableCollection<Models.Drug> Drugs
{
get { return DrugsValue; }
set
{
SetPropertyValue((() => Drugs), ref DrugsValue, value);
}
}
#endregion
#region Methods
private void LoadData()
{
if (ctx == null)
ctx = new Data.DBContext();
//load record count if we don't have it.
if (RecordCount == 0)
{
IsBusy = true;
Task.Run(() =>
{
if (Filters == null || Filters.Count() == 0)
{
RecordCount = ctx.Set<Entities.Drug>().Count();
}
else
{
RecordCount = ctx.Set<Entities.Drug>().Where(Filters).Count();
}
IsBusy = false;
LoadData();
});
return;
}
IsBusy = true;
Task.Run(() =>
{
if (Filters == null || Filters.Count() == 0)
currentPage = (from d in ctx.Query<Entities.Drug>()
orderby d.NDC
select d)
.Skip(PageSize * Page)
.Take(PageSize).ToList();
else
currentPage = (from d in ctx.Query<Entities.Drug>()
orderby d.NDC
select d)
.Where(Filters)
.Skip(PageSize * Page)
.Take(PageSize)
.ToIList() as List<Entities.Drug>;
Drugs = new ObservableCollection<Models.Drug>((from c in currentPage
select new Models.Drug(c)));
IsBusy = false;
});
}
#endregion
}

mvvm how to pass data from one view model to another view model

I have One View which has one Data grid with radio Button , onchecking radio Box , the selected row should go to other View Screen Textbox
here is my first ViewModel
public class CampaignSearchResultsViewModel : ViewModelBase
{
public CampaignSearchResultsViewModel(List<Lead> obj)
{
foreach(Lead lead in obj)
{
SelectedLead = lead;
}
}
public CampaignSearchResultsViewModel()
{
this.Commands.Add("CheckedCommand", new ActionCommand<Lead>(CheckIt));
Commands.Add("OutboundSelect", new ActionCommand<Object>(OutboundSelection));
_leads = new ObservableCollection<Lead>();
}
public ICommand OutboundSelect
{
get
{
return Commands["OutboundSelect"];
}
}
public void OutboundSelection(Object obj)
{
}
private void CheckIt(Lead lead)
{
SelectedLead = lead;
LeadViewModel lmv = new LeadViewModel(this);
}
#region Private
private ObservableCollection<Lead> _leads;
public bool IsChecked { get; set; }
private ICommand _checkedCommand;
private object _testProperty;
private Lead _selectedLead;
private ICollectionView icv;
#endregion
private ICommand _checkedRadioCommand;
private bool _inboundChecked;
#region Properties
public ObservableCollection<Lead> Leads
{
get { return _leads; }
set
{
_leads = value;
FirePropertyChanged("Leads");
}
}
public Lead SelectedLead
{
get { return _selectedLead; }
set { _selectedLead = value; }
}
public ICommand CheckedCommand
{
get
{
return Commands["CheckedCommand"];
}
}
public bool InboundChecked
{
get
{
return _inboundChecked;
}
private set
{
if (_inboundChecked != value)
{
_inboundChecked = value;
FirePropertyChanged("InboundChecked");
}
}
}
#endregion
}
i have to map SelectedLead to the other view model i have pass info to SearchCampaignMembers() method , how
public partial class LeadViewModel : ViewModelBase
{
public void SearchCampaignMembers()
{
_service.Load(_service.SearchCampaignMembersQuery(Entity.FirstName, Entity.LastName), lo =>
{
if (!lo.HasError)
{
ListLead = lo.Entities.ToList();
_savedLeadStatusId = Entity.LeadStatusId;
EntitySet = _service.Leads;
if (ListLead.Count == 1)
{
if (Entity != null)
{
IsVendorLead = Entity.LeadTypeId == Lookups.LeadType.VendorLead;
//Lead Update History
EntityQuery<LeadUpdateHistory> historyquery = null;
historyquery = _service.GetLeadUpdateHistoryByLeadIdQuery(Entity.LeadId);
_service.Load(historyquery, l =>
{
if (!l.HasError)
{
EntityHistory = _service.LeadUpdateHistories;
}
}, null);
//Lead Assignment
EntityQuery<LeadsAssignment> assignmentquery = null;
assignmentquery = _service.GetLeadsAssignmentByLeadIdQuery(Entity.LeadId);
_service.Load(assignmentquery, l =>
{
if (!l.HasError)
{
EntityAssignment = _service.LeadsAssignments;
}
}, null);
if (Entity.LeadTypeId == Lookups.LeadType.PhoneLead)
{
IsInboundLead = Entity.VendorId == null;
IsOutboundLead = Entity.VendorId != null;
}
else
{
IsInboundLead = false;
IsOutboundLead = false;
}
//SelectTimeToCall(Entity);
if (IsOutboundLead)
SelectedCampaign = Entity.LeadCampaigns.FirstOrDefault().Campaign;
else
SelectCampaign(Entity);
OperationsListener listener = new OperationsListener();
listener.Completed += (s, args) =>
{
CompleteInitializing();
//SwitchTab(param.InitialTab);
Action action = () =>
{
SelectDealer(Entity);
};
//GetDealerRecommendation(Entity.Address.ZipCode, action);
SelectStatus(Entity);
//if (callback != null)
// callback();
};
LoadLookupData(listener);
listener.Start();
}
}
else if (ListLead.Count >= 1)
{
CampaignSearchResultsViewModel vm = new CampaignSearchResultsViewModel();
foreach (Lead lead in ListLead)
{
vm.Leads.Add(lead);
ObservableCollection<Lead> abc;
abc = new ObservableCollection<Server.DataAccess.Lead>();
}
ViewController.OpenDialog("SearchCampaignResults", vm, r =>
{
});
}
else if (ListLead.Count == 0)
{
ViewController.OpenDialog("NoResults", (r) =>
{
});
}
}
else
{
//if (callback != null)
// callback();
}
}, null);
}
}
If you use MVVM Light Toolkit, see Messenger class see this answer for sample.

Simple animation in MVVM WPF application

I have a MVVM application that has a slider bar and when the user changes the slider bar it updates a graphic on the screen and updates some plots. This all works when the user changes the position of the slider, I would like to add a 'Play' button that automatically moves the slider and everything updates. I have tried the following code to do that and when I try it nothing changes on the screen. I have confirmed that it is indeed running the code and changing the 'SliderPos' variable. What am I missing?
private void VSMPlayer()
{
SliderPos = 0;
const int speed = 1;
while (SliderPos < SliderLength)
{
Thread.Sleep(100 / speed);
SliderPos = SliderPos + 20;
}
// todo finish this function
}
For clarity's sake here is the SliderPos property
public double SliderPos
{
get
{
return this.sliderPos;
}
set
{
this.sliderPos = value;
SetCursorLocation();
SetParameters();
this.RaisePropertyChanged("SliderPos");
}
}
The class owning SliderPos needs to implement INotifyPropertyChanged. (If your Slider.Value is bound to that property)
Edit: This alone does not work, as Will correctly noted the UI-Thread is sleeping.
You could try something like this, it works:
SliderPosition = 0;
DispatcherTimer timer = null;
timer = new DispatcherTimer(TimeSpan.FromSeconds(0.1), DispatcherPriority.Render, delegate
{
SliderPosition += 20;
if (SliderPosition > 100) timer.Stop();
},
Dispatcher.CurrentDispatcher);
timer.Start();
Edit2: If you are not modifying any UI-Thread-Owned controls you can just use any thread apart from the UI-Thread, e.g.:
SliderPosition = 0;
new Thread(new ThreadStart(delegate
{
while (SliderPosition < 100)
{
Thread.Sleep(100);
SliderPosition += 20;
}
})).Start();
Look at this similar solution:
ViewModel:
public class MainVM : INotifyPropertyChanged
{
public int SliderLength
{
get
{
return Names.Count - 1;
}
}
private void VSMPlayer()
{
SliderPos = 0;
const int speed = 1;
while (SliderPos < SliderLength)
{
Thread.Sleep(100 / speed);
SliderPos = SliderPos + 1;
}
// todo finish this function
}
private bool CanVSMPlayer()
{
return Names.Count > 0;
}
public ICommand Play
{
get
{
return new RelayCommand(() =>
{
IAsyncResult result = new Action(VSMPlayer).BeginInvoke((c =>
{
//operation completed
}), null);
}, CanVSMPlayer);
}
}
public ObservableCollection<string> Names
{
get
{
return new ObservableCollection<string>() { "a", "b", "c", "d", "e", "f", "g", "h", "i" };
}
}
int _sliderPos = 0;
public int SliderPos
{
get { return _sliderPos; }
set
{
_sliderPos = value;
RaisePropertyChanged("SliderPos");
RaisePropertyChanged("ActiveName");
}
}
public string ActiveName
{
get
{
if (SliderPos < Names.Count)
{
return Names[SliderPos];
}
else
{
return Names[0];
}
}
}
void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
View:
<StackPanel>
<TextBlock Text="{Binding ActiveName}"/>
<Slider Value="{Binding SliderPos}" Maximum="{Binding SliderLength}"/>
<Button Content="Play" Command="{Binding Play}"/>
</StackPanel>

Resources