/
anna_grinchenko
/
ReactiveUISample
Обзор
Документация
Войти
/
anna_grinchenko
/
ReactiveUISample
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ViewModels/MainWindowViewModel.cs
244 строки
10 KB
GrinchenkoAnna
Можно отредактировать группу по ключу. Контакты получают полный префикс, группа - первую букву префикса
06 июл 2026, 15:26
06 июл 2026, 15:26
5ffebbb
Код
Авторство
О чём код?
using DynamicData; using DynamicData.Binding; using ReactiveUI; using ReactiveUI.SourceGenerators; using ReactiveUISample.Models; using ReactiveUISample.Services; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; namespace ReactiveUISample.ViewModels { public partial class MainWindowViewModel : ReactiveObject, IActivatableViewModel { private readonly IContactsService _contactsService; public ViewModelActivator Activator { get; } = new ViewModelActivator(); public Interaction<ContactViewModel, ContactDialogResultDto?> EditContactInteraction { get; } = new(); private readonly SourceCache<ContactViewModel, string> _contactsCache = new(c => c.Email); private readonly ReadOnlyObservableCollection<ContactViewModel> _contacts; public ReadOnlyObservableCollection<ContactViewModel> Contacts => _contacts; private readonly ReadOnlyObservableCollection<ContactGroupViewModel> _groupedContacts; public ReadOnlyObservableCollection<ContactGroupViewModel> GroupedContacts => _groupedContacts; [Reactive] private string _searchText = string.Empty; [Reactive] private ContactViewModel? _selectedContact; private ContactViewModel? _previousSelected; [ObservableAsProperty] public partial bool IsLoading { get; } [Reactive] private string _statusMessage = string.Empty; public ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> AddContactCommand { get; } public ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> DeleteContactCommand { get; } public ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> LoadWithWhenAllCommand { get; } public ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> LoadWithWhenAnyCommand { get; } public ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> LoadWithParallelForEachCommand { get; } public ReactiveCommand<ContactViewModel, System.Reactive.Unit> EditContactCommand; public MainWindowViewModel(IContactsService contactsService) { _contactsService = contactsService; var contacts = contactsService.LoadContacts().Select(c => new ContactViewModel(c)); _contactsCache.AddOrUpdate(contacts); var filter = this.WhenAnyValue(x => x.SearchText) .Throttle(TimeSpan.FromMilliseconds(300)) .Select(BuildFilter); _contactsCache.Connect() .Filter(filter) .ObserveOn(RxApp.MainThreadScheduler) .Bind(out _contacts) .Subscribe(); var groupFilter = this.WhenAnyValue(x => x.SearchText) .Throttle(TimeSpan.FromMilliseconds(300)) .Select(BuildFilter); _contactsCache.Connect() .Filter(groupFilter) .Group(x => x.Name?.Length > 0 ? x.Name[..1].ToUpperInvariant() : "#") .Sort(SortExpressionComparer<IGroup<ContactViewModel, string, string>>.Ascending(g => g.Key)) .Transform(group => { var items = new ObservableCollectionExtended<ContactViewModel>(); group.Cache.Connect() .Sort(SortExpressionComparer<ContactViewModel>.Ascending(c => c.Name)) .ObserveOn(RxApp.MainThreadScheduler) .Bind(items) .Subscribe(); return new ContactGroupViewModel(group.Key, items, this); }) .Bind(out _groupedContacts) .Subscribe(); var canAdd = this.WhenAnyValue(x => x.IsLoading, loading => !loading); AddContactCommand = ReactiveCommand.Create(AddContact, canAdd); var canDelete = this.WhenAnyValue( x => x.SelectedContact) .Select(selected => selected is not null); DeleteContactCommand = ReactiveCommand.Create(DeleteContact, canDelete); //LoadWithWhenAllCommand = ReactiveCommand.CreateFromTask(LoadWithWhenAll); //LoadWithWhenAnyCommand = ReactiveCommand.CreateFromTask(LoadWithWhenAny); //LoadWithParallelForEachCommand = ReactiveCommand.CreateFromTask(LoadWithParallelForEach); EditContactCommand = ReactiveCommand.CreateFromTask<ContactViewModel>(EditContactAsync); Observable.Return(false).ToProperty(this, x => x.IsLoading); this.WhenActivated(disposables => { this.WhenAnyValue(x => x.SelectedContact) .Subscribe(selected => { if (_previousSelected is not null) _previousSelected.IsSelected = false; if (selected is not null) selected.IsSelected = true; _previousSelected = selected; }) .DisposeWith(disposables); }); } private Func<ContactViewModel, bool> BuildFilter(string? search) { if (string.IsNullOrWhiteSpace(search)) return _ => true; return c => c.Name.Contains(search, StringComparison.OrdinalIgnoreCase); } public void UpdateContactInCache(ContactViewModel contactVM) { _contactsCache.AddOrUpdate(contactVM); } private void AddContact() { var now = DateTime.Now.Ticks; var newContact = new Contact { Name = $"New Contact {now}", Email = $"new_{now}@example.com" }; _contactsCache.AddOrUpdate(new ContactViewModel(newContact)); } private void DeleteContact() { if (SelectedContact is not null) _contactsCache.Remove(SelectedContact); } private async Task EditContactAsync(ContactViewModel contactVM) { var result = await EditContactInteraction.Handle(contactVM); if (result is not null) { var oldEmail = contactVM.Email; contactVM.Name = result.Name; contactVM.Email = result.Email; contactVM.IsNew = result.IsNew; _contactsCache.RemoveKey(oldEmail); _contactsCache.AddOrUpdate(contactVM); } } #region WhenAll, WhenAny, Parallel.ForEach //private async Task LoadWithWhenAny() //{ // using var cts = new CancellationTokenSource(); // try // { // var task1 = _contactsService.LoadContactsFromSourceAsync("SourceA", cts.Token); // //var task2 = _contactsService.LoadContactsFromSourceAsync("SourceB", cts.Token); // var task2 = _contactsService.LoadContactFaultyAsync(); // var results = await Task.WhenAny(task1, task2); // cts.Cancel(); // var contacts = await results; // StatusMessage = $"WhenAny: первый ответ от {contacts.First().Name} (всего {contacts.Count})"; // } // catch (OperationCanceledException) // { // StatusMessage = $"WhenAny задача отменена (первый ответ уже получен)"; // } // catch (Exception ex) // { // StatusMessage = $"Ошибка WhenAny: {ex.Message}"; // } //} //private async Task LoadWithWhenAll() //{ // try // { // var task1 = _contactsService.LoadContactsFromSourceAsync("SourceA"); // //var task2 = _contactsService.LoadContactsFromSourceAsync("SourceB"); // var task2 = _contactsService.LoadContactFaultyAsync(); // var results = await Task.WhenAll(task1, task2); // var allContacts = results.SelectMany(x => x).ToList(); // StatusMessage = $"Загружено через WhenAll: {allContacts.Count} контактов"; // } // catch (Exception ex) // { // StatusMessage = $"Ошибка WhenAll: {ex.Message}"; // } //} //private async Task LoadWithParallelForEach() //{ // try // { // var contacts = _contactsService.LoadContacts().ToList(); // var processedNames = new ConcurrentBag<string>(); // await Task.Run(() => // { // Parallel.ForEach(contacts, contact => // { // var length = contact.Name.Length; // processedNames.Add($"{contact.Name} - ({length})"); // }); // }); // StatusMessage = $"Обработано через Parallel.ForEach: {contacts.Count} имен"; // } // catch (AggregateException ex) // { // var messages = ex.InnerExceptions.Select(e => e.Message); // StatusMessage = $"Ошибки Parallel.ForEach: ${string.Join("; ", messages)}"; // } // catch (Exception ex) // { // StatusMessage = $"Ошибка Parallel.ForEach: {ex.Message}"; // } //} #endregion } }