Where do I put the logic for my ICommand? - silverlight

I've recently started using the MVVM pattern in silverlight, and i'm not sure if i am using it correctly.
GUI
I currently have a MainView that has combobox of stock market sectors. When the user selects a sector (eg ENERGY) and clicks the Add button a list of stocks for that sector are displayed in a listbox. By the side of each stock in the listbox is a remove button that allows you to remove the individual stock from the listbox.
I have implemented the following ViewModels. (Below is just an indication of the code)
public class MainViewModel
{
public SectorViewModel CurrentSector
{
get;
set;
}
public string SelectedSector
{
get;
set;
}
public void AddSectorClickedCommand()
{
CurrentSector = new SectorViewModel(SelectedSector);
}
}
public class SectorViewModel
{
public ObservableCollection<StockViewModel> Stocks = new ObservableCollection<StockViewModel>();
public SectorViewModel(string sector)
{
List<Stocks> stocklist = StockProvider.GetStocks(sector);
for each (var s in stocklist)
{
StockViewModel svm = new StockViewModel(s);
svm.Remove+= { //Remove svm from Stocks collection logic
Stocks.add(svm);
}
}
}
My question is; in whcih viewmodel is it best to add the code implementation for the Remove button of each row in the listbox?? The Remove button should remove the StockViewModel from the SectorViewModel.Stocks collection.
I have currently added the RemoveClicked method to the StockViewModel(as shown above). This code fires an event back to the SectorViewModel and the RemoveStock method of the SectorViewModel removes the StockViewModel from the Stock collection.
Is there a better way to implement this remove functionality? I'm new to MVVM and am not sure if this is the best approach to develop this functionility, since the SectorViewModel needs to register to events of a StockViewModel.

Personally I don't like events because you should unsubscribe from them and also they can be used where it isn't appropriate.
I would use the constructor parameter to handle the remove command, something like this:
public class StockViewModel
{
public StockViewModel(Stock stock, Action<StockViewModel> removeCommandAction)
{
//...
this.RemoveCommand = new DelegateCommand(() => removeCommandAction(this));
}
}
public class SectorViewModel
{
public SectorViewModel()
{
//...
StockViewModel svm = new StockViewModel(s, this.RemoveStock);
Stocks.add(svm);
}
private void RemoveStock(StockViewModel stock)
{
//...
}
}
Another approach is to use some kind of the EventAggregator pattern, for example, the Messenger class from the MVVM light Toolkit. But I think that it is an overkill for such simple task:
public StockViewModel(Stock stock, IMessenger messenger)
{
//...
this.RemoveCommand = new DelegateCommand(() =>
messenger.Send(new NotificationMessage<StockViewModel>(this, RemoveItemNotification)));
}
public SectorViewModel(IMessenger messenger)
{
//...
messenger.Register<NotificationMessage<StockViewModel>>(this, msg =>
{
if (msg.Notification == StockViewModel.RemoveItemNotification)
{
this.RemoveStock(msg.Content);
}
}
}
Also I heard that Silverlight 5 supports binding to a relative source.
So there is the 3rd approach. I'm not sure whether this example works, but at least it should:
<Button Content="Remove"
Command="{Binding DataContext.RemoveCommand RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}" />
public class SectorViewModel
{
public SectorViewModel()
{
this.RemoveCommand = new DelegateCommand(obj => this.RemoveStock((StockViewModel)obj));
}
public ICommand RemoveCommand { get; set; }
}
The last example is the most preferred by the way and is used in WPF applications because WPF has always had RelativeSource binding.

Related

How to display new modal form in ReactiveUI 6.5

I am one of a team of developers currently maintaining a large suite of applications written using the WinForms UI.
In order to improve testability of our applications, we are wanting to move to an MVVM style, to separate the UI from the business logic. However, we need to keep using the WinForms UI, to minimize impact on our users as they work with different applications in the suite.
In trialing ReactiveUI, I have got a handle on how to bind form controls and commands to my view model, but cannot find documentation or examples on how to pop up a modal form to ask for or display additional information. For example these documentation pages on routing mention every supported UI framework except WinForms: http://docs.reactiveui.net/en/user-guide/routing/index.html, https://github.com/reactiveui/ReactiveUI/blob/docs/docs/basics/routing.md
Unfortunately, the ReactiveUI "good examples page" does not appear to have any WinForms-based examples, and all the other ReactiveUI / WinForms examples I can find using Google are only a single form.
I definitely want to keep forms/views out of the view model to maintain testability.
I believe the right way is to have a ReactiveCommand that is triggered by some user action in the view (such as clicking a button, selecting a menu item), but:
What should the command do?
Should it use Routing even though WinForms is not mentioned in the documentation? If yes, how is Routing done in a WinForms application?
How would the command/routing request the new form gets shown modally?
For simple messages and yes/no answers, I would look at Wayne Maurer's example for using UserError. I've used his example in Winform projects.
For something more complex, I was having the same difficulties finding any Winforms examples for routing. My google searches finally landed me in the source code for ReactiveUI.Winforms, where I discovered that Paul already has a UserControl for Winforms that will host routed UserControl views. It's called RoutedControlHost.
Using that code, I hacked something together that will show modal forms. I'm sure this isn't the best way to do it, but it might give you ideas.
RoutedModalHost
using Microsoft.Win32.SafeHandles;
using ReactiveUI;
using System;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ReactiveUI_Test_Routing
{
public class RoutedModalHost : ReactiveObject, IDisposable
{
readonly CompositeDisposable disposables = new CompositeDisposable();
RoutingState _Router;
IObservable<string> viewContractObservable;
public RoutedModalHost()
{
this.ViewContractObservable = Observable.Return(default(string));
var vmAndContract =
this.WhenAnyObservable(x => x.Router.CurrentViewModel)
.CombineLatest(this.WhenAnyObservable(x => x.ViewContractObservable),
(vm, contract) => new { ViewModel = vm, Contract = contract });
Form viewLastAdded = null;
this.disposables.Add(vmAndContract.Subscribe(x => {
if (viewLastAdded != null)
{
viewLastAdded.Dispose();
}
if (x.ViewModel == null)
{
return;
}
IViewLocator viewLocator = this.ViewLocator ?? ReactiveUI.ViewLocator.Current;
IViewFor view = viewLocator.ResolveView(x.ViewModel, x.Contract);
view.ViewModel = x.ViewModel;
viewLastAdded = (Form)view;
viewLastAdded.ShowDialog();
}, RxApp.DefaultExceptionHandler.OnNext));
}
[Category("ReactiveUI")]
[Description("The router.")]
public RoutingState Router
{
get { return this._Router; }
set { this.RaiseAndSetIfChanged(ref this._Router, value); }
}
[Browsable(false)]
public IObservable<string> ViewContractObservable
{
get { return this.viewContractObservable; }
set { this.RaiseAndSetIfChanged(ref this.viewContractObservable, value); }
}
[Browsable(false)]
public IViewLocator ViewLocator { get; set; }
bool disposed = false;
SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
handle.Dispose();
// Free any other managed objects here.
//
this.disposables.Dispose();
}
// Free any unmanaged objects here.
//
disposed = true;
}
}
}
MainViewModel
using ReactiveUI;
using System.Reactive.Linq;
using System;
namespace ReactiveUI_Test_Routing
{
public class MainViewModel : ReactiveObject, IScreen
{
public RoutingState Router { get; private set; }
public ReactiveCommand<object> ShowTestModalForm { get; protected set; }
public MainViewModel(RoutingState modalRouter)
{
Router = modalRouter;
ShowTestModalForm = ReactiveCommand.Create();
ShowTestModalForm.Subscribe(x => Router.Navigate.Execute(new TestModalFormViewModel(this)));
}
}
}
MainView
using System.Windows.Forms;
using Splat;
using ReactiveUI;
namespace ReactiveUI_Test_Routing
{
public partial class MainView : Form, IViewFor<MainViewModel>
{
public MainView()
{
InitializeComponent();
IMutableDependencyResolver dependencyResolver = Locator.CurrentMutable;
dependencyResolver.Register(() => new TestModalFormView(), typeof(IViewFor<TestModalFormViewModel>));
RoutingState router = new RoutingState();
RoutedModalHost modalHost = new RoutedModalHost();
modalHost.Router = router;
this.BindCommand(ViewModel, vm => vm.ShowTestModalForm, v => v.ShowTestModalForm);
ViewModel = new MainViewModel(router);
}
public MainViewModel ViewModel { get; set; }
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MainViewModel)value; }
}
}
}
TestModalFormViewModel
using ReactiveUI;
namespace ReactiveUI_Test_Routing
{
public class TestModalFormViewModel : ReactiveObject, IRoutableViewModel
{
public IScreen HostScreen { get; protected set; }
public string UrlPathSegment { get { return "ModalForm"; } }
public TestModalFormViewModel(IScreen screen)
{
HostScreen = screen;
}
}
}

UWP - Refreshing listview and CollectionViewSource

I have created ChatMessageGroup and ChatMessageGroupCollection and a ListView with ItemsSource set to CollectionViewSource:
<ListView x:Name="ChatMessageLv" ItemsSource="{Binding SelectedChat.ChatMessageGroupCollection.Cvs.View}" ItemTemplateSelector="{StaticResource ChatMessageDataTemplateSelector}">
public class ChatMessageGroup : IGrouping<DateTime, ChatMessage>, INotifyCollectionChanged
{
private ObservableCollection<ChatMessage> _chatMessages;
public DateTime Key { get; set; }
public ObservableCollection<ChatMessage> ChatMessages
{
get { return _chatMessages; }
set
{
if (_chatMessages != null)
_chatMessages.CollectionChanged -= CollectionChanged;
_chatMessages = value;
_chatMessages.CollectionChanged += CollectionChanged;
}
}
public ChatMessageGroup()
{
ChatMessages = new ObservableCollection<ChatMessage>();
}
public IEnumerator<ChatMessage> GetEnumerator()
{
return ChatMessages.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
public class ChatMessageGroupCollection : IEnumerable<ChatMessageGroup>
{
private readonly ObservableCollection<ChatMessageGroup> _groups;
public ObservableCollection<ChatMessage> Source { get; set; }
public CollectionViewSource Cvs { get; set; }
public ChatMessageGroupCollection(ObservableCollection<ChatMessage> messages)
{
Source = messages;
messages.CollectionChanged += Messages_CollectionChanged;
var groups = messages
.GroupBy(GetGroupKey)
.Select(x => new ChatMessageGroup()
{
Key = x.Key,
ChatMessages = x.OrderBy(GetGroupKey).ToObservableOrDefault()
})
.OrderBy(x => x.Key);
_groups = new ObservableCollection<ChatMessageGroup>(groups);
Cvs = new CollectionViewSource() { IsSourceGrouped = true, Source = _groups };
}
...
Everything works fine in here except the changes inside group collection:
_groups.Add(new ChatMessageGroup()); -> this line reflect changes in ListView
but if I do like this: _groups[0].ChatMessages.Add(new ChatMessage()) it doesn't work even though ChatMessageGroup is implementing INotifyCollectionChanged and is raised every time ChatMessages ObservableCollection is changed.
The workaround is to update ChatMessages and remove group from _groups and then add it again but it's not a solution. Refresh() on CollectionViewSource is not available in UWP. Are there any other solutions?
Whilst this doesn't technically qualify as an answer to the question I do think it might qualify as an architectural answer.
The way to make a grouped list view update based on the underlying collection changing in WPF is as simple as setting IsLiveGroupingRequested to true on the CollectionViewSource instance declared in XAML.
As I've been working my way through UWP (after nearly a decade of WPF) I've come to the conclusion that Microsoft are suggesting through omission that this isn't the right approach to the problem for UWP. So going to the lengths of implementing the feature yourself could be construed as missing the point somewhat.
In my particular situation I've decided to change my approach entirely and implement the rendering as multiple instance of ListViews as opposed to forcing an old paradigm onto a new platform.
The result of this actually made me arrive at a solution that improved my UX to boot.
Food for thought ...

Reactive UI with Datagrids

Currently my RadDatagrid1 has a Cell_click action for the RadDatagrid1, and when a ClientName is selected, that client info is projected in DataGrid2.
code within Mouse Double Click:
private void Cell_click(object sender, GridViewSelectedCellsChangedEventArgs e)
{
Var Details = (from info in contacts
where info.ClientName = sender.CurrentCell.ToString()
select new {info.ClientName, info.ClientAddress, Info.ClientNumber});
DataGrid2.ItemsSource = Details.ToList();
}
This is currently what i have but, it should be a reactive UI.
An example of reactitve UI i was told to look at was this in the GridViewModel:
this.WhenAny(x => x.Forename, x => x.Surname, x => x.City, (p1, p2, p3) => Unit.Default).Subscribe(x => Filter());
But that doesn't quite make sense to me. If I could get guidance and tips how to convert this to reactive UI please.
I am a newbie to Reactive UI and my experience so far has been through trial and error, due to the lack of documentation. So my method below might not be correct.
Make sure you have a ViewModel backing your WPF control (see this page)
Your ViewModel should look something like:
public class ViewModel : ReactiveObject {
// ClientInfo type is the type of object you want to bind to the DataGrid
private ClientInfo clientInfo;
public ClientInfo ClientInfo {
set { this.RaiseAndSetIfChanged(ref clientInfo, value); }
get { return clientInfo; }
}
// Move contacts IEnumerable/IQueryable to your ViewModel
private IQueryable<ClientInfo> contacts;
public LoadInfo(string clientName) {
ClientInfo = (from info in contacts
where info.ClientName = clientName
select new {info.ClientName, info.ClientAddress, Info.ClientNumber})
}
}
Make sure your View (the control class) implements IViewFor<T> where T is the type of your View Model. Bind views according to the documentation here.
Do something like this for your View:
// Implement the methods on that interface, which I will not include below
public partial class View : IViewFor<ViewModel> {
private ICommand loadClientInfo;
public View() { // constructor
InitializeComponent(); // Don't forget this
// Binds the data in your ViewModel with the ItemSource of your DataGrid
this.OneWayBind(ViewModel, vm => vm.ClientInfo, x => x.DataGrid2.ItemSource);
}
private void Cell_click(object sender, GridViewSelectedCellsChangedEventArgs e)
{
ViewModel.LoadInfo(sender.CurrentCell.ToString());
}
}

hiearchical data, catel, and MVVM

I'm working with Catel, MVVM, WPF and am wondering about how to work with nested/hiearchical data.
Let's say from a database I've got a list of Customers, each with a list of Invoices, each with a list of InvoiceItems. Customers own many Invoices which own many InvoiceItems.
I've got a working solution, but I do not like it. My approach was to build a collection of classes that would act a kind of like an ado.net “dataset.” A class would represent each layer of the hiearchy.
This top level class, CustomerModel, would contain a collection of of InvoiceBlocks:
CustomerModel
ObservableCollection of < InvoicesBlocks >
Each InvoceBlock would contain an Invoice and a collection of InvoiceItems:
InvoiceBlock
Invoice
ObservableCollection of < InvoiceItems >
It seemed clever until wading through the databinding path= satements. There are also times when I have to loop through the sets mamaully to update totals, defeating a major selling point of MVVM.
So, I've decided to learn more about grouping with LINQ queries and databinding. Is this the way the pros do it?
What you can do is make each view model responsible for using the right services to retrieve the data.
Note that I did not use Catel properties to make it easy to understand, but you can simply use Catel.Fody or rewrite the properties to get Catel properties.
public class CustomerViewModel
{
private readonly IInvoiceService _invoiceService;
public CustomerViewModel(ICustomer customer, IInvoiceService invoiceService)
{
Argument.IsNotNull(() => customer);
Argument.IsNotNull(() => invoiceService);
Customer = customer;
_invoiceService = invoiceService;
}
public ICustomer Customer { get; private set; }
public ObservableCollection<IInvoice> Invoices { get; private set; }
protected override void Initialize()
{
var customerInvoices = _invoiceService.GetInvoicesForCustomer(Customer.Id);
Invoices = new ObservableCollection<IInvoice>(customerInvoices);
}
}
public class InvoiceViewModel
{
private readonly IInvoiceService _invoiceService;
public InvoiceViewModel(IIinvoice invoice, IInvoiceService invoiceService)
{
Argument.IsNotNull(() => invoice);
Argument.IsNotNull(() => invoiceService);
Invoice = invoice;
_invoiceService = invoiceService;
}
public IInvoice Invoice { get; private set; }
public ObservableCollection<IInvoiceBlock> InvoiceBlocks { get; private set; }
protected override void Initialize()
{
var invoiceBlocks = _invoiceService.GetInvoiceBlocksForInvoice(Invoice.Id);
InvoiceBlocks = new ObservableCollection<IInvoiceBlock>(invoiceBlocks);
}
}
Now you are fully in control what happens when.

MVVM - Filling a combobox and how to relay commands from window to user control

I have an ItemType that is coming from EF. This ItemType is wrapped in a ItemTypeViewModel. Many ItemTypes are wrapped in ItemTypeViewModels and are being put in a ObservableCollection in the ViewModel for the user control that will display them:
I use the CollectionView so I can page through them. The screen looks like this:
Now I'm thinking that the buttons that are used for paging that are in the user control could better be placed in the Window that will contain the user control. So, in my user control I know have commands like this:
But I want them to be in the window. I don't know if this will be good design, but if I will go through with this, how to relay the commands from the window to the usercontrol?
Another question I have is how to fill the combobox in the user control. They will always have the same values, but the selected item will change per ItemType.
I know two ways how to do this.
1) Add new class, for example, MainWindowViewModel and add there 2 commands and an instance of UserControlViewModel (you haven't said the title, so I will call it in this way). Here is a part of example:
public class MainWindowViewModel
{
public UserControlViewModel ChildControlViewModel { get; set; }
private Lazy<RelayCommand> nextCommand = new Lazy<RelayCommand>(() =>
new RelayCommand(
() => this.ChildControlViewModel.CollectionView.MoveCurrentToNext(),
() => this.ChildControlViewModel.CollectionView.CurrentPosition < this.ChildControlViewModel.ItemTypes.Count - 1));
public ICommand NextCommand
{
get { return nextCommand.Value; }
}
//prev command...
}
I have used the Lazy class, but the main idea is clear: the code is the same, except the call this.ChildControlViewModel.CollectionView instead of CollectionView.
2) Use the Messenger class.
This way isn't so obvious and it has only one advantage: the viewmodels are loosely connected.
public class MainWindowViewModel
{
public const string NextCommandNotification = "NextCommand";
public const string PreviousCommandNotification = "PreviousCommand";
private bool isNextCommandEnabled;
private bool isPreviousCommandEnabled;
public MainWindowViewModel()
{
this.NextCommand = new RelayCommand(
() => Messenger.Default.Send(new NotificationMessage<MainWindowViewModel>(this, NextCommandNotification)),
() => this.isNextCommandEnabled);
//prev command...
Messenger.Default.Register<NotificationMessage<UserControlViewModel>>(this,
msg =>
{
if (msg.Notification == UserControlViewModel.CurrentItemChangedNotification)
{
this.isNextCommandEnabled = msg.Content.CollectionView.CurrentPosition < msg.Content.ItemTypes.Count - 1;
this.NextCommand.RaiseCanExecuteChanged();
//prev command...
}
});
}
public ICommand NextCommand { get; private set; }
//prev command...
}
public class UserControlViewModel
{
public const string CurrentItemChangedNotification = "CurrentItemChanged";
public UserControlViewModel()
{
Messenger.Default.Register<NotificationMessage<MainWindowViewModel>>(this,
msg =>
{
if (msg.Notification == MainWindowViewModel.NextCommandNotification)
this.CollectionView.MoveCurrentToNext();
else if (msg.Notification == MainWindowViewModel.PreviousCommandNotification)
this.CollectionView.MoveCurrentToPrevious();
});
this.CollectionView.CurrentChanged += (s,e) => Messenger.Default.Send(new NotificationMessage<UserControlViewModel>(this, CurrentItemChangedNotification))
}
}
I'm not sure whether this code will work correctly. And it is not easy to explain.
The MainWindowViewModel class send the message when a user press the button. The UserControlViewModel class process the message, change the position of the current item, and send the CurrentItemChangedNotification message. The MainWindowViewModel class process this message and updates the CanExecute part of the command.
1st solution is better for me, but at the same time I use the Messenger class quite often. It depends on the situation.

Resources