Introduction
I am trying to cover my C# WPF application into an MVVM pattern using Structuremap IoC container for dependency injection.
My code is works well until I try to use the same command binding in UserControls as Windows.
I tried the following
If I try to bind a command in a certain UserControl, I got the following error: System.Windows.Data Error: 40 : BindingExpression path error: 'HelloWorldCommand' property not found on 'object' ''MainWindowViewModel' (HashCode=7304143)'. BindingExpression:Path=HelloWorldCommand; DataItem='MainWindowViewModel' (HashCode=7304143); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
So the problem is that my HelloWorldCommand is it in my CustomUserControlViewModel which contained and binded by MainWindowViewModel.
My custom code snippet
My code is 90% same to the following tutorial:
Part 1
Part 2
Only the ObjectFactory method is different, which can be seen below:
public sealed class ObjectFactory
{
public static IContainer Container { get; private set; }
private static Action<ConfigurationExpression> _initialiseMethod;
private static readonly Lazy<IContainer> _containerBuilder =
new Lazy<IContainer>(CreateContainer, LazyThreadSafetyMode.ExecutionAndPublication);
public static void Initialise()
{
Container = _containerBuilder.Value;
}
private static IContainer CreateContainer()
{
return new Container(config =>
{
#region services
config.For<IFileDisplayerService>().Singleton().Use<FileDisplayerService>();
config.For<IWatermarkService>().Singleton().Use<WatermarkService>();
#endregion
#region windows
config.For<IWindow>().Use<MainWindow>();
config.For<IWatermarkWindow>().Use<WatermarkSettingsWindow>();
config.For<IMainWindow>().Singleton().Use<MainWindow>();
config.For<IMainWindowViewModel>().Singleton().Use<MainWindowViewModel>();
config.For<IWatermarkSettingsWindow>().Singleton().Use<WatermarkSettingsWindow>();
config.For<IWatermarkSettingsWindowViewModel>().Singleton().Use<WatermarkSettingsWindowViewModel>();
#endregion
#region views
config.For<IFileListView>().Use<FileListView>();
config.For<IFileListViewModel>().Use<FileListViewModel>()
.Ctor<IView>().Is<FileListView>();
config.For<IFileDisplayerView>().Use<FileDisplayerView>();
config.For<IFileDisplayerViewModel>().Use<FileDisplayerViewModel>()
.Ctor<IView>().Is<FileDisplayerView>();
#endregion
});
}
}
My question
My concrete question is that how can I bind a command to a user control which has an own- and a parent ViewModel? This is not shown in the example above.
I think the parent ViewModel should contain the command as well, but I don't know how can I pass it to the parent ViewModel from child ViewModel.
Thank you mm8 I solved my problem like the following mode:
MainWindowViewModel:
public IViewModel FileListViewModel { get; set; }
public IViewModel FileDisplayerViewModel { get; set; }
public IView FileListView { get; set; }
public IView FileDisplayerView { get; set; }
public MainWindowViewModel(IWindow window, IContainer container,
IFileDisplayerViewModel fileDisplayerViewModel, IFileListViewModel fileListViewModel) : base(window, container)
{
FileListViewModel = fileListViewModel;
FileListView = FileListViewModel.View;
FileDisplayerViewModel = fileDisplayerViewModel;
FileDisplayerView = FileDisplayerViewModel.View;
}
Now I can bind my UserControl's ViewModel into UserControlView:
<Button Command="{Binding FileListViewModel.HelloWorldCommand}" Width="100" Height="20" Content="Push" Background="White"></Button>
This is not entirely what I wanted but it saves me from catastrophic spaghetti code. I think it is feasible that I have a child view collection which is used to automatically bind a command from them if it is not available in the parent view. But this is sufficient now to proceed the project. Thank you!
Related
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.
I am using WPF and I have a custom datagrid. What I would like to do is add a property (CustomGridCommands) to that grid which I can set in xaml from any view.
What I have at the moment is as follows (I have changed the code a bit to simplify it):
Custom grid C# code:
public class CustomWPFDataGrid : DataGrid, INotifyPropertyChanged
{
public static readonly DependencyProperty CustomContextMenuCommandsProperty =
DependencyProperty.Register("CustomContextMenuCommands",
typeof (ObservableCollection<WPFBaseCommand>),
typeof (CustomWPFDataGrid));
[Bindable(true)]
public ObservableCollection<WPFBaseCommand> CustomContextMenuCommands
{
get { return (ObservableCollection<WPFBaseCommand>) GetValue(CustomContextMenuCommandsProperty); }
set { SetValue(CustomContextMenuCommandsProperty, value); }
}
...
...
}
XAML code:
<common:CustomWPFDataGrid
ItemsSource="{Binding Path=ItemList}"
CustomContextMenuCommands="{Binding Path=CustomGridCommands, Mode=TwoWay}">
....
</common:CustomWPFDataGrid >
The object I have bound to the view that contains the grid is as follows:
public class TestViewModel
{
public ObservableCollection<TestDisplayViewModel> ItemList { get; set; }
public ObservableCollection<WPFBaseCommand> CustomGridCommands;
public TestViewModel()
{
... population of objects here
}
When I run this, and check the value of the property (CustomContextMenuCommands) in the datagrid, it is always null.
Any ideas what I am doing wrong?
EDIT
The setter of the "CustomContextMenuCommands" is never hit.
CustomGridCommands in your ViewModel is a field, View cannot use it. If you make it a public property, then it will become accessible. More details on what can be used as binding source can be found on MSDN - Binding Sources.
If using WPF 4.5, static properties can also be used for binding, as described in release notes.
Using Prism, MVVM with View First View Injection: Unable to load Image in Module
I am using WPF and Prism in an MVVM pattern using the View First, View Injection pattern.
In my solution I have a SG.BannerModule project in which I just want to bind an Image in my BannerView.xaml.
I use the below BannerViewModel that implements IBannerViewModel because I am using containers to resolve my BannerViewModel.
Here is the IBannerViewModel interface:
public interface IBannerViewModel : IViewModel
{
Uri BannerImageUri { get; set; }
}
I exposed the BannerImageUri because I am resolve the BannerViewModel thru the container in the
BannerMainModule using the Interface.
The BannerViewModel implementation is
public class BannerViewModel : ViewModelBase, IBannerViewModel
{
#region Properties
private IUnityContainer _container;
private ILogger _logger;
private Uri _bannerImageUri;
public Uri BannerImageUri
{
get { return new Uri("pack://SG.Website:,,,SG.BannerModule;component/Assets/SGBanner2.png"); }
set
{
if (value != _bannerImageUri)
{
_bannerImageUri = value;
OnPropertyChanged("BannerImageUri");
}
}
}
#endregion
#region Constructors
public BannerViewModel(IBannerView bannerView, IUnityContainer container)
: base(bannerView)
{
View = bannerView;
View.ViewModel = this;
_container = container;
_logger = _container.Resolve<ILogger>();
_logger.WriteToLogs("BannerViewModel Constructor");
}
#endregion
}
The Image is located in the Assets directory of my SG.BannerModule project.
I have set the build properties for the Image SGBanner2.png to be a resource and have added the image in the resources tab of the Properties Pane for the SG.BannerModule project.
I have set the data context of the BannerView.xaml in the BannerView.xaml.cs file in the following way because I am using View Injection.
public partial class BannerView : UserControl, IBannerView
{
public BannerView()
{
InitializeComponent();
}
public IViewModel ViewModel
{
get
{
return (IBannerViewModel) DataContext;
}
set { DataContext = value; }
}
}
Because of the View First View Injection pattern I set the DataContext in my in code behind and do not set any in the XAML.
My question is
Because I am binding this image, do I need to use an ImageSource converter? If this is the case, will it be a problem because the image is a png and not a bitmap?
Or is the problem the Pack Uri. Because I am using this module in a region in my SG.WebSite project, I am not sure that my pack Uri is correct but I have not been able to trouble shoot correctly why my image is not showing up in my shell window.
Stumped?
Thanks!
Just so you know, it appears you are using ViewModel first. Having your ViewModel resolve your View and then probably calling region.Add(viewModel.View).
This is the packURI syntax for local resource files:
pack://application:,,,/Subfolder/ResourceFile.xaml
For reference resource files:
pack://application:,,,/ReferencedAssembly;component/Subfolder/ResourceFile.xaml
How do we inject IRegionManager in the ViewModel using MEF Container. I have to switch view in my ViewModel's Command delegate. Here is the brief description of what I am doing. I have an entity called Product whose list is displayed in one View (ProductListView). In that view the user can select the Product and click on Edit button. This would switch the view and present a new View(ProductEditView). For activating a different view, I would need a reference to IRegionManager something like this
public class ProductListVM : NotificationObject { //The Product List View Model
[Import]
public IRegionManager RegionManager { get; set; }
private void EditProduct() { //EditCommand fired from ProductListView
IRegion mainContentRegion = RegionManager.Regions["MainRegion"];
//Switch the View in "MainContent" region.
....
}
}
The above code fails with NullReferenceException for RegionManager. This seems logical because the above View Model is constructed by WPF through DataContext property in Xaml and DI doesn't come into play, so it doesn't get a chance to import the RegionManager instance. How do we resolve the IRegionManager in this scenario.
The Container instance can be exported in the bootstrapper using following
container.ComposeExportedValue<CompositionContainer>(container);
Then in the viewmodel, the IRegionManager instance can be imported using the code
IServiceLocator serviceLocator = ServiceLocator.Current;
CompositionContainer container = serviceLocator.GetInstance<CompositionContainer>();
RegionManager = container.GetExportedValue<IRegionManager>();
However, referring a View in ViewModel is a violation of the MVVM pattern. But since I was following an article here to learn Prism , I had to get along the same. Also the article was in Silverlight and I had to find a way to import RegionManager in wpf, which is little different.
regards,
Nirvan.
Try using [ImportingConstructor] like this:
public class ProductView : Window
{
private IProductViewModel viewModel;
[ImportingConstructor]
public ProductView(IProductViewModel ViewModel)
{
this.viewModel = ViewModel;
this.DataContext = this.viewModel;
}
}
public class ProductViewModel: IProductViewModel, INotifyPropertyChanged
{
private IRegionManager regionManager;
private ICommand editUserCommand;
[ImportingConstructor]
public ProductViewModel(IRegionManager InsertedRegionManager)
{
this.regionManager = InsertedRegionManager;
editUserCommand = new DelegateCommand(ExecuteEditUserCommand, CanExecuteEditUserCommand);
}
public ICommand EditUserCommand
{
get {return this.editUserCommnad;}
}
private bool CanExecuteEditUserCommand()
{
return true;
}
private void ExecuteEditUserCommand()
{
this.regionManager......
}
}
I try to adopt entity-level validation (attributes validation on properties on entities) by create ViewModel that expose that Entity.
public class MyViewModel
{
public MyEntity MyEntity { get; set; }
}
I set binding in xaml to, this xaml page set its DataContext to instance of MyViewModel
TextBlock Text="{Binding MyEntity.MyProperty}"
When I load MyEntity from database and set it to MyViewModel, nothing happen. I also call NotifyPropertyChanged("MyEntity"); and it still nothing happen.
I try again by create MyProperty in MyViewModel
public class MyViewModel
{
private MyEntity MyEntity { get; set; }
public string MyProperty
{
get { return this.MyEntity.MyProperty; }
set { this.MyEntity.MyProperty = value; }
}
}
And changed xaml to bind to MyProperty. This time when I call NotifyPropertyChanged("MyProperty "); View get update correctly, when I input incorrect data, it has ValidationErrors at MyEntity but View don't raise that error (not show red border)
I want to know how can I get entity-level validation working with MVVM.
Hi
you must change the definition of ViewModel such as
public class MyViewModel:IDataErrorInfo
{
}
and implement interface.
this force View to show red border on error.
wish to help.