I'm trying to learn ReactiveUI in WPF and I'm confusing on how to bind command using Reactive UI binding (not default Xaml binding).
I read on ReactiveUI documentation that the correct way is to use the following instruction:
this.BindCommand(this.ViewModel, vm => vm.MyCommand, v => v.myControl);
Now if I have in MainWindowView.xaml (View):
<Button x:Name="TestButton" Command="{Binding Click}" />
in MainWindowView code-behind:
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
and in MainWindowViewModel (ViewModel):
class MainWindowViewModel : ReactiveObject
{
public ReactiveCommand<Unit, Unit> ClickCommand { get; }
public MainWindowViewModel()
{
ClickCommand = ReactiveCommand.Create(ClickMethod);
}
void ClickMethod()
{
// Code for executing the command here.
}
}
I don't know where insert and how to compose the first instruction :
this.BindCommand(this.ViewModel, vm => vm.MyCommand, v => v.myControl);
for my specific context.
Thank you very much for and an answer.
The WPF samples referenced by Rodney Littles in the comment above are very good. For your case it should be something like this:
public partial class MainWindowView : ReactiveWindow<MainWindowViewModel>
{
public MainWindowView()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
this
.WhenActivated(disposables => {
this
.BindCommand(this.ViewModel, vm => vm.ClickCommand, v => v.TestButton)
.DisposeWith(disposables);
});
}
}
Make sure you derive from ReactiveWindow<MainWindowViewModel> instead of Window. Also, instead of DataContext, use the inherited property named ViewModel.
Related
I'm new to WPF and I'm trying to start a little project with a maximum of good practice. I'm using MVVM and dependency injection.
I have a concern which seems to be easy to understand but i can't find an answer (at this step, DataContext is not very clear for me).
The UserControlView of type UserControl contains just a button for testing.
This is the app class :
public App()
{
IServiceCollection services = new ServiceCollection();
services.AddSingleton<MainWindow>();
services.AddSingleton<UserControlViewModel>();
services.AddSingleton<UserControlView>();
_serviceProvider = services.BuildServiceProvider();
}
The user control is included in the Main windows like that :
<Grid>
<views:UserControlView/>
</Grid>
Now, in the OnStartup overrided method :
protected override void OnStartup(StartupEventArgs e)
{
MainWindow = _serviceProvider.GetRequiredService<MainWindow>();
MainWindow.DataContext = _serviceProvider.GetRequiredService<PaymentMeansViewModel>();
MainWindow.Show();
}
Like that it works, my button is correctly binded to the command.
But what is strange for me is that I have to set the 'UserControlViewModel' as the DataContext of the Main Window.
Isn'it possible to bind it to the 'UserControlView', something like :
protected override void OnStartup(StartupEventArgs e)
{
MainWindow = _serviceProvider.GetRequiredService<MainWindow>();
UserControlView testUC = _serviceProvider.GetRequiredService<UserControlView>();
testUC.DataContext = _serviceProvider.GetRequiredService<UserControlViewModel>();
MainWindow.Show();
}
Thanks for help.
Finally I did it.
I think (I hope I'm right) that I understood.
First of all, let's begin with the basic.
A view must have a viewmodel to bind the properties. A usercontrol is a kind of view "encapsulated" in a view. Therefore a usercontrol must have its own viewmodel and the view must have its own viewmodel.
The datacontext of the MainWindow is set in the app onstartup method :
MainWindow = new MainWindow()
{
DataContext = new MainWindowViewModel()
};
MainWindow must implement INotifyPropertyChanged. All view models must implement this interface. We can create a base class which will be derived in the view models :
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string? propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The DataContext of the usercontrol must be explicit in the xaml of the MainWindow:
<Grid>
<views:UserControlView DataContext="{Binding CurrentViewModel}"/>
</Grid>
"CurrentViewModel" is a DataContext, then it's a ViewModel, and as it is binded, it must be a property of the MainViewModel.
public class MainWindowViewModel : ViewModelBase
{
public ViewModelBase CurrentViewModel { get; }
public MainWindowViewModel()
{
CurrentViewModel=new UserControlViewModel();
}
}
Hope it can help.
Trying to understand how to bind this static list to a combobox that located on different window.
public partial class MainWindow : Window
{
public static List<Classes.Entity> EntityList { get; set; }
public MainWindow()
{
EntityList = new List<Classes.Entity>();
InitializeComponent();
}
...
the object:
public class Entity
{
public string entityName { get; set; }
...
XAML (In a diffrent window, call "NewRelationship.xaml.cs"
<ComboBox x:Name="cb_from" ItemsSource="{Binding Path=EntityList}" DisplayMemberPath="entityName" SelectedValue="{Binding Path=Entity}" />
Of course I fill the list later in the code...
if I moving the list to the newRelationship window and add "this.datacontext = this;" its working,
How do I make this work when the list is in the mainWindow? Thanks...
A better approach would be to keep the EntityList in a separate object that both windows could reference:
class ViewModel
{
private List<Classes.Entity> _entityList = new List<Classes.Entity>();
public IEnumerable<Classes.Entity> EntityList
{
get { return _entityList; }
}
}
partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
When the second window is created, you can pass an instance of the ViewModel class to it, and set it as the DataContext.
I have an issue with something that should be very simple databinding scenario. I want to bind a list of items. I want to create a user control put it in a ItemsControl's template and bind the ItemsControl to some data. I am perfectly happy with one time databinding so I was kind of hoping to avoid learning about dependency properties and all the databinding stuff for this simple scenario.
Here is the XAML for the user control:
<TextBlock>Just Something</TextBlock>
And the code behind:
namespace TestWindowsPhoneApplication
{
public partial class TestControl : UserControl
{
public TestData SomeProperty { get; set; }
public String SomeStringProperty { get; set; }
public TestControl()
{
InitializeComponent();
}
}
}
MainPage.xaml:
<ItemsControl Name="itemsList" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<t:TestControl SomeStringProperty="{Binding Path=SomeString}"></t:TestControl>
<!--<TextBlock Text="{Binding Path=SomeString}"></TextBlock>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is MainPage.xaml.cs:
namespace TestWindowsPhoneApplication
{
public class TestData
{
public string SomeString { get; set; }
}
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
itemsList.DataContext = new TestData[] { new TestData { SomeString = "Test1" }, new TestData { SomeString = "Test2" } };
}
}
}
When I run the project I get an error "the parameter is incorrect". I also tried binding directly to the item with SomeProperty={Binding} since that is what I actually want to do but this causes the same error. If I try doing the same thing with the TextBlock control (the commented line) everything works fine.
How can I implement this simple scenario?
To make a property on your custom control "bindable" you have to make it a dependency property. Check out my answer here for a nice simple example of doing just this on a custom control: passing a gridview selected item value to a different ViewModel of different Usercontrol
public string SomeString
{
get { return (string)GetValue(SomeStringProperty); }
set { SetValue(SomeStringProperty, value); }
}
public static readonly DependencyProperty SomeStringProperty =
DependencyProperty.Register("SomeString", typeof(string), typeof(TestControl),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnSomeStringChanged)));
private static void OnSomeStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TestControl)d).OnSomeStringChanged(e);
}
protected virtual void OnSomeStringChanged(DependencyPropertyChangedEventArgs e)
{
//here you can do whatever you'd like with the updated value of SomeString
string updatedSomeStringValue = e.NewValue;
}
I have a WPF application using MVVM; when I change the ViewModel in my main window ViewModel class, the new user control is not displayed in the window... the original one remains. The ViewModel looks like this:
public class MainWindowViewModel : ViewModelBase
{
public ViewModelBase Workspace;
public MainWindowViewModel()
{
var w = new CustomerDetailsViewModel();
SetActiveWorkspace(w);
}
void NavigationService_ViewChanged(object sender, ViewChangedEventArgs e)
{
SetActiveWorkspace(e.View);
}
void SetActiveWorkspace(ViewModelBase workspace)
{
Workspace = workspace;
}
}
My XAML looks like this:
< ContentControl Content="{Binding Path=Workspaces}" >
The navigation service ViewChanged event is firing, and the SetActiveWorkspace method is being called with the correct view in the argument. However, after that, the view is not reloaded. What am I missing here?
Your Workspace property is not raising the PropertyChanged event. It should look like this:
private ViewModelBase _workspace;
public ViewModelBase Workspace
{
get { return _workspace; }
set
{
if (value != _workspace)
{
_workspace = value;
// This raises the PropertyChanged event to let the UI know to update
OnPropertyChanged("WorkSpace");
}
}
}
Make sure your ViewModelBase implements INotifyPropertyChanged
The problem we are having is that we cannot get binding to work in our
prism silverlight application when using the view-model first
approach. The view first approach work fine. We have gone over the
official documentation and various web sites, but have still not
resolved the issue. Below is the code for both the view-model first,
and the view first approach. Are we missing something? Read about it on my blog http://silvercasts.blogspot.com
View-Model first approach:
Bootstrapper:
internal void RegisterLoginRegionAndView()
{
IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion,
() => Container.Resolve<IViewModel>().View);
}
ViewModel:
public ViewModel(IView view)
{
View = view;
View.SetModel(this);
User = new User();
User.Username = "TestUser";
}
ViewModel Interface:
public interface IViewModel
{
IView View { get; set; }
}
View Interface:
public interface IView
{
void SetModel(IViewModel model);
}
View Xaml:
<TextBox x:Name="Username" TextWrapping="Wrap" Text="{Binding User.Username}" />
View Code Behind:
public void SetModel(IViewModel viewModel)
{
this.DataContext = viewModel;
}
View first approach
Bootstrapper:
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion, typeof(IView));
ViewModel:
public ViewModel()
{
User = new User();
User.Username = "TestUser";
}
View Code Behind:
public View(IViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
Your implementation of SetModel on your view needs to be as follows:
public void MyUserControl : UserControl, IView
{
//...
public void SetModel(IViewModel vm)
{
this.DataContext = vm;
}
}
If that's not there, it needs to be (you haven't posted your implementation of SetModel, but this would be the source of the issue in this case).
If this is not the issue, it's likely because your ViewModel does not implement INotifyPropertyChanged. I usually use a base ViewModel that does this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then all of my ViewModels derive from that:
public class MyViewModel : ViewModelBase
{
private User _user;
public User User
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("User");
}
}
}
Note: in your case the "User" object should probably also be a ViewModel and also raise OnPropertyChanged for the Username property.
Hope this helps.
The obvious difference to me is that you set the DataContext in the "view first" approach, but not in the "view model first" approach. I'm not sure if Prism sets the DataContext for you (I'd guess that you're assuming that it does) but try setting the DataContext manually to see if this is the problem. In your ViewModel constructor you call View.SetModel(this) - does that call set the DataContext?
The problem was that I was using the SetModel method before the data object was instanced. Moving it like this:
public ViewModel(IView view)
{
View = view;
User = new User();
User.Username = "TestUser";
View.SetModel(this);
}
solved the problem.