I am trying to learn ReactiveUI. I have been looking at the wpf sample project in the repository https://github.com/reactiveui/ReactiveUI/tree/master/samples/getting-started
I decided to try to convert this to winforms but have a problem.
In the wpf example the search function populates, via OAPH, a property of the 'Main' ViewModel (AppViewModel) called SearchResults which is an IEnumerable of 'Child' ViewModels (NuGetDetailsViewModel)
public IEnumerable<NugetDetailsViewModel> SearchResults => _searchResults.Value;
in the 'Main' View (MainWindow) there is a listbox which has its ItemSource bound to the SearchResults i.e. the IEnumerable of ViewModels.
There seems to be some wizardry going on that finds and displays the appropriate View for the given ViewModel. It even says so in the comments:
In our MainWindow when we register the ListBox with the collection of NugetDetailsViewModels if no ItemTemplate has been declared it will search for a class derived off IViewFor and show that for the item.
In winforms I think I have two problems, but I maybe out by one.. or more:
It doesnt seem like the same wizardry of finding the View for the ViewModel is working, however this may be due to problem two.
How can I bind the IEnumerable of ViewModels to a winforms control?
In winforms I am using a flowlayoutpanel in place of the ListBox and have tried several variations of:
this.OneWayBind(ViewModel, vm => vm.ResultsList, v => v.flowLayoutPanel1.DataBindings)
I have been able to use some conversion code directly in the View to update the flowLayoutPanel directly but it requires direct knowledge of the Child View and doesn't sit well with me, and isn't as automatic as I would like.
this.OneWayBind(ViewModel,
vm => vm.ResultsList,
v => v.flowLayoutPanel1,
selector: value =>
{
this.flowLayoutPanel1.Controls.Clear();
foreach (var value in values)
{
this.flowLayoutPanel1.Controls.Add(new AssemblyInfoView() { ViewModel = value });
}
return this.flowLayoutPanel1;
} ));
For clarity the 'Child' View linked to my 'Child' ViewModel also derives from ReactiveUserControl.
I use the following code to register the Views:
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetCallingAssembly());
and have checked that they are registered.
If anyone is able to help that would be appreciated.
As an addendum if anyone knows of some more complex sample projects using ReactiveUI particularly using winforms that would be very helpful.
Thank you.
In ReactiveUI 9.11 there is a new feature which allows you to bind to either any control that has Control.ControlCollection or to a TableLayoutControlCollection
This will allow you to have it automatically add to the Control's.
This is made available by a new interface called ISetMethodBindingConverter which allows you to override how the 'Set' in our binding engine works.
There is now a sample for the WinForms application found here: https://github.com/reactiveui/ReactiveUI.Samples/tree/master/winforms/ReactiveDemo
Related
I have a custom control in the early stages of development as I endeavour to learn about wpf custom control development. The custom control inherits from ItemsControls which gives me access to an ItemsSource property to which I am binding an enumerable collection.
Currently I have a simple two project solution comprising my custom control in one and a test project in the other to test the former. In my test project I have a simple mainwindow onto which I have put my custom control and bound its ItemsSource.
<WpfControls:VtlDataNavigator x:Name="MyDataNavigator"
ItemsSource="{Binding ElementName=MainWindow, Path=Orders}" />
In the loaded event of the main window (which implements INotifyPropertyChanged) I instantiate the Orders collection. The customcontrols gets initialised before the main window loads but I can see from examining the Live Visual Tree in visual studio that once the main form loads the custom controls Items Source property is indeed set to Orders. Now of course I'd actually like to count the orders and have my custom control display that (it's a simple data navigator so what I'm after is the record count). I know how to get the count but how do I know when the itemsSource has changed so that I can react to it and get the count. There's no itemsSourceChanged event that I can see.
I've seen this blog article, but I'm wondering if there is a more straightforward approach to this as it seems such an obvious thing to want to know about.
You can do that using OverrideMetaData.
Try this:
public class Class1 : ItemsControl
{
static Class1()
{
ItemsSourceProperty.OverrideMetadata(typeof(Class1),
new FrameworkPropertyMetadata(null, OnItemSourceChanged));
}
private static void OnItemSourceChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Why you haz Changed me!");
}
}
The ItemsSource is a DependencyProperty, and when creating DPs you can optionally specify a "property changed" event. Unfortunately ItemsSource is locked away in the base class, so I started wondering if there might be a way to add your own event to an existing DP. I came across this article that looks promising. Basically you would do something like this (untested so read the article!):-
var dpd = DependencyPropertyDescriptor.FromProperty(
VtlDataNavigator.ItemsSourceProperty,
typeof(VtlDataNavigator));
if (dpd != null)
{
dpd.AddValueChanged(
vtlDataNavigatorInstance,
delegate
{
var count = VtlDataNavigatorInstance.ItemsSource.Count; // Or whatever...
});
}
I am re-factoring an old winforms project into using MVVM via ReactiveUI. The binding part works great so far except the Grid.Datasource = ReactiveList doesn't seem to give any update on changing/adding/deleting.
I just wanted to confirm here, since the DataGridView.Datasource support databinding list only, ReactiveUI.ReactiveList will not work here? or there could be some possible get arounds.
I've just been struggling with this, the approach I've come up with is to wrap a ReactiveList on the ViewModel with a ReactiveDerivedBindingList.
I've created a BindingSource based on the item ViewModel class for design time support, then I replace this at runtime:
private void CreateDerivedBindings()
{
this.Articles = this.ViewModel.Articles.CreateDerivedBindingList(x => x);
this.ViewModel.Articles.ItemChanged.Subscribe(_ => this.Articles.Reset());
//this.Articles.ChangeTrackingEnabled = true;
this.articlesDataGridView.DataSource = this.Articles;
}
private IReactiveDerivedBindingList<ArticleViewModel> Articles { get; set; }
Subscribing to ItemChanged of the underlying ReactiveList (which has ChangeTrackingEnabled turned on) enables items in the grid to update as they are changed. Setting ChangeTrackingEnabled on the derived binding list didn't do anything. This is a brute force approach, I'd guess causing a full refresh of the grid, maybe there is a more finessed approach.
A simpler approach could be to use a ReactiveBindingList on the ViewModel, but I've not tried this as the class is winforms specific and I'm aiming for a ViewModel which could be reused with WPF.
ReactiveList supports INotifyCollectionChanged, I don't know of any other way that lists can signal they have changed. I could be misinformed about Winforms Grid though!
I'm writting a form in WPF/c# with the MVVM pattern and trying to share data with a user control. (Well, the User Controls View Model)
I either need to:
Create a View model in the parents and bind it to the User Control
Bind certain classes with the View Model in the Xaml
Be told that User Controls arn't the way to go with MVVM and be pushed in the correct direction. (I've seen data templates but they didn't seem ideal)
The usercontrol is only being used to make large forms more manageable so I'm not sure if this is the way to go with MVVM, it's just how I would of done it in the past.
I would like to pass a class the VM contruct in the Xaml.
<TabItem Header="Applicants">
<Views:ApplicantTabView>
<UserControl.DataContext>
<ViewModels:ApplicantTabViewModel Client="{Binding Client} />
</UserControl.DataContext>
</Views:ApplicantTabView>
</TabItem>
public ClientComp Client
{
get { return (ClientComp)GetValue(ClientProperty); }
set { SetValue(ClientProperty, value); }
}
public static readonly DependencyProperty ClientProperty = DependencyProperty.Register("Client", typeof(ClientComp),
typeof(ApplicantTabViewModel),
new FrameworkPropertyMetadata
(null));
But I can't seem to get a dependancy property to accept non static content.
This has been an issue for me for a while but assumed I'd find out but have failed so here I am here.
Thanks in advance,
Oli
Oli - it is OK (actually - recommended) to split portions of the View into UserControl, if UI became too big - and independently you can split the view models to sub view models, if VM became too big.
It appears though that you are doing double-instantiations of your sub VM. There is also no need to create Dependency Property in your VM (actually, I think it is wrong).
In your outer VM, just have the ClientComp a regular property. If you don't intend to change it - the setter doesn't even have to fire a property changed event, although it is recommended.
public class OuterVm
{
public ClientComp Client { get; private set; }
// instantiate ClientComp in constructor:
public OuterVm( ) {
Client = new ClientComp( );
}
}
Then, in the XAML, put the ApplicantTabView, and bind its data context:
...
<TabItem Header="Applicants">
<Views:ApplicantTabView DataContext="{Binding Client}" />
</TabItem>
I answered a similar question as yours recently: passing a gridview selected item value to a different ViewModel of different Usercontrol
Essentially setting up a dependency property which allows data from your parent view to persist to your child user control. Abstracting your view into specific user controls and hooking them using dependency properties along with the MVVM pattern is actually quite powerful and recommended for Silverlight/WPF development, especially when unit testing comes into play. Let me know if you'd like any more clarification, hope this helps.
I have a few questions regarding to building WPF MVVM applications.
1) I'm using ICollectionView objects for databound controls such as ListView and ComboBox. I found this was the simplest way of gaining access to/tracking the selected item of these controls. What is the best way to replace the contents of ICollectionView? Currently I'm doing it like so:
private ICollectionView _files;
public ICollectionView Files {
get { return _files; }
}
void _service_GetFilesCompleted(IList<SomeFile> files) {
this.IsProcessing = false;
_files = CollectionViewSource.GetDefaultView(files);
_files.CurrentChanged += new EventHandler(FileSelectionChanged);
OnPropertyChanged("Files");
}
I didn't know whether it was necessary to reattach the handler every time I refresh the list of files?
2) Now that I've got my head round it, I am starting to like the MVVM pattern. However, one concept I'm not completely sure about is how to send notifications back down to the view. Currently I am doing this by binding to properties on my ViewModel. For example, in the above code I have an "IsProcessing" property that I use to determine whether to display a ProgressBar. Is this the recommended approach?
3) Following on from 2) - there doesn't seem to be a standard way to handle exceptions in an MVVM application. One thought I had was to have one method on my ViewModel base class that handled exceptions. I could then inject an IMessagingService that was responsible for relaying any error messages. A concrete implementation of this could use MessageBox.
4) I have a few tasks that I want to perform asynchronously. Rather than adding this logic directly in my service I created a decorator service that runs the underlying service methods on a new thread. It exposes a number of events that my ViewModel can then subscribe to. I have listed the code below. I understand that BackgroundWorker is a safer option but did not know whether it was suitable for running multiple asynchronous tasks at once?:
public void BeginGetFiles()
{
ThreadStart thread = () => {
var result = _serviceClient.GetUserFiles(username, password);
GetFilesCompleted(result.Files);
};
new Thread(thread).Start();
}
Finally, I realize that there are a number of MVVM frameworks projects that handle some of these requirements. However, I want to understand how to achieve the above using built-in functionality.
Thanks
If you have ListViews and ComboBoxes, you should really be considering an ObservableCollection<> to bind to these controls. Adding and removing items from the collection will automatically notify the control the property has changed.
If you're doing background processing, you can look at the BackgroundWorker or DispatcherTimer to handle updates to the UI. These both have the capability of acting on the UI thread, and can be thread safe.
To get the selected item from a combo box, expose an INotifyCollectionChanged object such as ObservableCollection and bind it to the itemsource, then create another property for the current item and binding ComboBox.SelectedItem (or ComboBox.SelectedValue if required) to it. You will need to manage the selection when updating the collection.
On the face of it, ICollectionView seems like the obvious choice but the WPF implementation really forces your hand on some threading code that you really shouldn't be troubled with.
I used ICollectionView and CollectionViewSource recently (for filtering) and have become frustrated with how many dispatcher issues have crept in. Today I am probably going to revert to the method i describe above.
I've created a new WPF project, and threw in a DataGrid. Now I'm trying to figure out the easiest way to bind a collection of data to it.
The example I downloaded seems to do it in the window c'tor:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
But then the bindings don't seem to appear in the Visual Studio's Properties window. I'm pretty sure there's a way to set the data context in XAML too... it would make me even happier if I could do it directly through the properties window, but all the Binding options are empty. What's the typical approach?
Edit: At 14 minutes, he starts to talk about other methods of setting the data context, such as static resources, and some "injection" method. I want to learn more about those!
What I typically do is use MVVM. You can implement a simplified version by setting the data context in your code behind and having a model type class that holds your data.
Example: In your code behind
DataContext = Model; // where Model is an instance of your model
then in your view
<DataGrid .... ItemsSource="{Binding SomeProperty}">....
Where SomeProperty is an enumerable property on your view model
You can also set a data context in XAML by using the DataContext property
<uc:SomeUserControl DataContext="{Binding AnotherProperty}"....
This will run your user control within the DataContext of the AnotherProperty on your model.
Note that this is grosely simplified but it'll get you on your way.
Have a look at the MVVM design pattern. This pattern is very suitable for wpf applications.
There is described where to store your data and how to bind your ui to the data.