I got a simple WinForm application with a couple of textboxes and a confirm button, I'm using ReactiveUI.
This is my ViewModel:
public CurrencyViewModel()
{
editCurrency = new Currency();
this.ValidationRule(
viewModel => viewModel.IsoCode,
isoCode => !string.IsNullOrWhiteSpace(isoCode),
"error");
this.ValidationRule(
viewModel => viewModel.Name,
name => !string.IsNullOrWhiteSpace(name),
"error");
NewCommand = ReactiveCommand.Create(() => NewItem());
SaveCommand = ReactiveCommand.Create(() => Save(), this.IsValid());
}
public string IsoCode
{
get => isoCode;
set
{
editCurrency.IsoCode = value;
this.RaiseAndSetIfChanged(ref isoCode, value);
}
}
public string Name
{
get => name;
set
{
editCurrency.Name = value;
this.RaiseAndSetIfChanged(ref name, value);
}
}
private void NewItem()
{
IsoCode = string.Empty;
Name = string.Empty;
Symbol = string.Empty;
}
I then bind my validation and my save command in the view:
this.BindValidation(ViewModel, vm => vm.IsoCode, v => v.errorLabelIsoCode.Text).DisposeWith(disposables);
this.BindValidation(ViewModel, vm => vm.Name, v => v.errorLabelName.Text).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCommand, v => v.sfButtonOk, nameof(sfButtonOk.Click)).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.NewCommand, v => v.sfButtonNew, nameof(sfButtonNew.Click)).DisposeWith(disposables);
My issue is that sfButtonOk stays enabled when i first launch the application even if isValid() is false, the command doesn't fire as intended so it's just a grapichal problem it seems. The button is disabled only if I write valid text and then cancel it.
It seems that the button is disabled only when isValid goes from true to false
The issue here is probably related to a view model being initialized too late, or due to the view model property not sending change notifications on the view side in time. Make sure you assign the view model to the IViewFor.ViewModel property before a call to WhenActivated, or otherwise implement INotifyPropertyChanged on the view side (also you probably don't need a WhenActivated at all because WinForms doesn't have dependency properties that might introduce a potential for a memory leak)
Additionally worth noting, that we have evergreen sample apps targeting various UI frameworks including Windows Forms in the ReactiveUI.Validation core repository https://github.com/reactiveui/ReactiveUI.Validation/blob/d5089c933e046c5ee4a13149491593045cda161a/samples/LoginApp/ViewModels/SignUpViewModel.cs#L43 Just tested the Winforms sample app, and the button availability seems to perform as we'd expect.
Related
The project (WPF) has these folders:
Views
ViewModels
SubViews
SubViewModels
How to get the Prism's ViewModelLocator working with them (Views> ViewModels & SubViews> SubViewModels), the solution I found only works with one convention:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
{
var viewName = viewType.FullName.Replace(".ViewModels.", ".CustomNamespace.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = $"{viewName}ViewModel, {viewAssemblyName}";
return Type.GetType(viewModelName);
});
}
You could opt for registration of the pairs (not as bad as it sounds because you have to register for navigation anyway).
Alternatively, you implement both conventions one after the other - take the view's name, subtract "Views" and add "ViewModels" and try to get the view model's type. If that fails, subtract "SubViews" and add "SubViewModels" and try again. You could even check for cross combinations (e.g. "SubViews.ViewA" and "ViewModels.ViewAViewModel")...
I solved it by checking the viewType and based on it, I return the appropriate ViewModel type:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
{
string prefix;
if (viewType.FullName.Contains(".SubViews."))
{
prefix = viewType.FullName.Replace(".SubViews.", ".SubViewModels.");
}
else
{
prefix = viewType.FullName.Replace(".Views.", ".ViewModels.");
}
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = $"{prefix}ViewModel, {viewAssemblyName}";
return Type.GetType(viewModelName);
});
}
I'm using ReactiveUI 9.21 and DynamicData 6.13 together with WinForms.
I did read https://dynamic-data.org/tag/dynamicdata/ and some other resources, but am still confused with IObservableCache, ReadOnlyObservableCollection and others when dealing with changes in DynamicData collections.
If I have a SourceCache<Result, string> recoResults in a model class - how can I sort it there, and then:
Q1: How do I expose SourceCache to be bound to a BindingList<Result> (in a view model for a view with a DataGridView)?
Q2: At the same time, how do I expose SourceCache to be able to react to IChangeSets manually (in a view with a non-reactive plot component)?
What I have:
In the moment, I expose the SourceCache unsorted as IObservableCache in the model and connect to it the views/view models:
public IObservableCache<Result, string> RecoResults { get; protected set; }
private readonly SourceCache<Result, string> recoResults;
public DropModel()
{
recoResults = new SourceCache<Result, string>(e => e.ID);
RecoResults = recoResults.AsObservableCache();
}
Sorting for the DataGridView's BindingList works in the view model:
ResultBindingList = new BindingList<RecoResult>();
DropModel.RecoResults.Connect()
.Sort(recoResultComparer)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(ResultBindingList)
.Subscribe();
But I don't know how to do the sorting in the view with the non-reactive plot component:
// Still unsorted results:
var observableResults = VM.DropModel.RecoResults.Connect();
// Add result:
var d2 = observableResults.WhereReasonsAre(ChangeReason.Add)
.ObserveOn(RxApp.MainThreadScheduler)
.ForEachChange(x => {
// Add to plot
})
.Subscribe();
// Remove result:
var d3 = observableResults.WhereReasonsAre(ChangeReason.Remove)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => {
// Remove from plot
});
What I'd like to have:
I'd like to do the sorting in a central place, because the user can choose between two ways of sorting and I want to sync it in all views. I'd like to expose the sorted results as a single property and use it for the BindingList as well as for manual change iterations.
In the model, I already tried:
public ??? SortedResults { get; protected set; }
recoResults.Connect()
.Sort(recoResultComparer)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(SortedResults)
.Subscribe();
Q3: What return type is the best to use for SortedResults?
And in general:
Q4: When do we use IObservableCache/IObservableList, and when do we use ReadOnlyObservableCollection? And when is IObservable<IChangeSet<T, key>> used?
My question is in regards to the "Compelling Example" given for ReactiveUI where as a person types in a search bar, the search occurs asynchronously. Suppose though I wanted to provide my user with a way to refresh the current search results. I could just ask them to backspace in the search bar and retype their last character. However, they are asking for a "Refresh" button because it's not obvious to them how to refresh the current results.
I can't think of how to do this within the context of the example:
public class TheViewModel : ReactiveObject
{
private string query;
private readonly ObservableAsPropertyHelper<List<string>> matches;
public TheViewModel()
{
var searchEngine = this.ObservableForProperty(input => input.Query)
.Value()
.DistinctUntilChanged()
.Throttle(TimeSpan.FromMilliseconds(800))
.Where(query => !string.IsNullOrWhiteSpace(query) && query.Length > 1);
var search = searchEngine.SelectMany(TheSearchService.DoSearchAsync);
var latestResults =
searchEngine.CombineLatest(search, (latestQuery, latestSearch) => latestSearch.Query != latestQuery ? null : latestSearch.Matches)
.Where(result => result != null);
matches = latestResults.ToProperty(this, result => result.Matches);
}
public string Query
{
get
{
return query;
}
set
{
this.RaiseAndSetIfChanged(ref query, value);
}
}
public List<string> Matches
{
get
{
return matches.Value;
}
}
}
Does anyone have any suggestions on how I could capture a command from a button and re-execute the existing search without clearing out their current search text?
You can merge the existing observable of Query changes with a new observable that returns the current Query when the refresh button is pressed.
First a command for the refresh button:
public ReactiveCommand<Unit, String> Refresh { get; private set; }
Then you create the command and assign it, and create a merged observable of the two observables:
Refresh = ReactiveCommand.Create<Unit, String>(() => Query);
var searchEngine = Observable.Merge(
this.ObservableForProperty(input => input.Query).Value().DistinctUntilChanged(),
Refresh)
.Throttle(TimeSpan.FromMilliseconds(800))
.Where(query => !string.IsNullOrWhiteSpace(query) && query.Length > 1);
The rest can stay unchanged.
I am using wpf busy indicator and setting its Isbusy property from viewModel. Aftersetting Isbusy property I want to filter my ICollectionview and push it on UI. This Filter operation I have put in
IsBusy = true;
await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new System.Action(() =>
{
this.MyCollectionView.Filter = item =>
{
Iitem IdName = item as Iitem;
return Regex.IsMatch(IdName.Name, SearchText, RegexOptions.IgnoreCase);
};
}));
Workaround: If I put Task.Delay(200).Wait(); after setting IsBusy, busy indicator will be displayed for some cases.
Filter always runs on the UI thread, keeping it too busy to actually update the UI to reflect the IsBusy state.
In most cases, Filter functions should run fast enough that they don't need any special handling. However, if you're sure that you need to run a time-consuming filter, then the best way to do it is to split into two different collections: MyCollection and MyFilteredCollection. Then, any time MyCollection (or the filter) changes, do something like this:
IsBusy = true;
var items = MyCollection.ToList();
var filter = SearchText;
MyFilteredCollection = await Task.Run(() =>
items.Where(x => Regex.IsMatch(x.Name, filter, RegexOptions.IgnoreCase)));
IsBusy = false;
(this assumes that IsBusy will prevent MyCollection and SearchText from changing).
I've already done it quite easily in the past with Silverlight, by declaring a BusyIndicator as my root element, and binding the IsBusy property to the IsLoading property of the domain context generated by RIA Services:
<toolkit:BusyIndicator IsBusy="{Binding Context.IsLoading}" >
Since there seems to be no IsLoading property on the ObjectContext generated by Entity Framework, how can I bind the IsBusy property in WPF?
Thank you
What I came up with:
Busy Indicator from the WPF Extended Toolkit:
<extoolkit:BusyIndicator IsBusy="{Binding IsBusy}" BusyContent="Loading data..." >
In my base class view model, I've added the following method:
protected void ExecuteBackgroundProcess(Action action)
{
IsBusy = true;
Task task = Task.Factory.StartNew(() => action()).ContinueWith((s) => this.IsBusy = false);
}
When I want to load a collection from the server, I can call from a derived view model:
this.ExecuteBackgroundProcess(() =>
{
var collection = _securityRepo.TakeOfType<Security>(10).ToObservableCollection();
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
Securities = collection;
RaisePropertyChanged("Securities");
});
});
There's also a more robust & complete solution on CodeProject:
http://www.codeproject.com/KB/WPF/ThreadingComponent.aspx?msg=3319891