I am developing an mvvm app with wpf. A requirement just got added on to block the user from changing tabs if a textbox has text.
What is the best way to do this completely in the viewmodel? I don't know how to block a tabitem because there is no dependencyobject command in the tabcontrol to tie into, do i need to roll my own tabcontrol and build an ICommand around the SelectionChanged event?
Should I simply (eegad..don't say it) put code in the code behind of the view in the SelectionChanged event?
Do I have an alternative that I haven't thought of?
You might consider binding each of the TabItems' IsEnabled property to a property in your ViewModel (e.g. ViewModel.TabsEnabled) and set that property to False when the textbox has text. That way, you'll be able to enable/disable those tabs from your ViewModel without having to have a code behind file for that particular view.
Which means you'll have something like the following in your view (assuming your ViewModel is a static class named ViewModel):
<TabItem IsEnabled="{Binding Source={x:Static local:ViewModel.TabsEnabled}}"/>
Then you just have to set the TabsEnabled property on the ViewModel when one of the textboxes has content; there are a couple of ways to do this, but if they are bound to your ViewModel you should have plenty of opportunities to listen for changes and set TabsEnabled as appropriate.
Related
I've been experimenting with WPF, Xaml, MVVM, and DependencyInjection lately. Consequently, I am creating a UI using MVVM principles. A certain portion of the UI is designed to act like a wizard wherein not all of the available options are presented to the user at the same time. Each section of options is its own View (sub-View) with a single View (Parent View) hosting these sub-Views in a ContentControl. The user sets certain options and uses buttons to move from one section to the other.
View Navigation
To switch between these views I'm using a DataTemplateSelector with each sub-View defined as a DataTemplate in my Xaml resources.
Content Control in the Main View:
<ContentControl Content="{Binding ElementName=ParentViewControl, Path=ViewState, Mode=TwoWay}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}" />
Example sub-View Data Template:
<DataTemplate x:Key="SubViewATemplate">
<local:SubViewAView x:Name="SVAView" DataContext="{Binding ElementName=ParentViewControl, Path=DataContext}" ViewState="{Binding ElementName=ParentViewControl, Path=ViewState, Mode=TwoWay }" />
</DataTemplate>
On the Parent View and each sub-View I've created a Dependency Property called ViewState (an enum). These bind to each other through the DataTemplates. In each View's code-behind I update this ViewState Property based off user input and it propagates up to the Parent View which in turn triggers the DataTemplateSelector. So far, so good. The navigation works beautifully.
ViewModel Info
The Parent View has a ViewModel which implements INotifyPropertyChanged as its DataContext. I'm attempting to use this single ViewModel to bind Properties to the Parent View and the sub-Views. The problem is the DataContext binding in the DataTemplate snippet above does not work. (Which is odd to me since the ViewState binding does.) After various attempts to get this to work, the DataContext on a sub-View is either null or the ViewState control variable.
I am currently using the UnityContainer as my dependency injector.
Various Attempts
Here are the various other things I've tried that have all failed:
1) Registered the ViewModel as a singleton in the UnityContainer thereby using Constructor Injection on the sub-Views to set the DataContext. (Does not work because there must be a Parameter-less Constructor for the DataTemplate resource.)
2) Registered the ViewModel as a singleton in the UnityContainer and then using Property Injection on the sub-Views to set the DataContext. (Does not work. I think this is due to the UnityContainer not working when an object is instantiated in Xaml.)
3) Creating sub-ViewModels for each sub-View that needs a ViewModel to display properties that would have existed on the Parent ViewModel. I've used this before to get around the Xaml instantiation problem with the UnityContainer. I then replace the DataContext binding in the DataTemplate with the associated sub-ViewModel. (Does not work because for some reason the DataContext of my Parent View is getting set to the ViewState variable instead of remaining my ViewModel which I've set in the view's Constructor. This in turn means the sub-ViewModel property on my Parent ViewModel can't be found to bind to the DataContext of the sub-View.) Are the Content and DataContext of UserControls the same thing? Does setting one affect the other?
4) Moving the ViewState dependency property from the View to the ViewModel and then setting the ContentControl's Content to bind to the ViewModel. This violates MVVM principles but by this time I was trying anything to get this to work. (It doesn't work because when ViewState is changed in the code-behind of the view the ViewModel does not trigger as changed.) I haven't gone any further with this one because I didn't want to go deeper violating MVVM.
Conclusion
I've found most of these attempted solutions on this site over the last couple days. I haven't had any formal training in WPF, Xaml, and MVVM so I suspect I'm missing something obvious, or am attempting to do something that isn't possible. I'm going to keep attempting variations on the above and researching until I find something that works, but I thought I would tap into the collective knowledge here to help me find a solution.
What I'd prefer is to have the group of Views use the single ViewModel as their DataContext so I can bind properties to their controls. And have the Views' navigation be controlled by a DataTemplateSelector. Is there a way to do this that I'm not seeing?
Thank you for your time!
I have had similar issues before, I have had good luck using a RelativeSource binding. Maybe try something like this:
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl},Path=DataContext}"
Just a thought.
I have a UserControl with 4 combobox bound to collections in viewmodel for that usercontrol.
I have used this control in a wpf form. This wpf form has its own viewmodel.
How do i access the text from the 4 comboboxes within the wpf form's viewmodel?
EDIT: i saw that you have different viewmodels. now it depends of the use of your usercontrol and the use of mvvm:)
you can use messenger or eventaggregator to comunicate the seleteditems from usercontrolviewmodel to mainviewmodel.
you can also use RelativeSource binding in your usercontrol to bind the selecteditem to your mainviewmodel directly (usercontrol then is just a composition of controls).
you can can rid of the usercontrol viewmodel and put all in the mainviewmodel and take my old example
you can create DependencyProperties for the SelectedItems in your usercontrol!(not usercontrol viewmodel!) and bind these to the properties in your mainviewmodel. i think thats the cleanest way if the usercontrol should be a real usercontrol.
old example:
in your viewmodel: //the real code should of course implement INotifyPropertyChanged and raise it properly
public ObservableCollection<string> MyFirstCollection {get; set;}//init once, add,remove,clear to alter
public string MySelectedCombobox1Value {get;set;}
in your usercontrol:
<ComboBox ItemsSource="{MyFirstCollection }" SelectedItem="{Binding MySelectedCombobox1Value, Mode=TwoWay}" />
thats all relating to your question. be sure that you set the DataContext right. you can check this with tools like snoop. the code i posted expected that the dataconext for the combobox is the viewmodel.
The UserControl should inherit the data context of the form you're adding it to which would be the view model. Any bindings in the UserControl would then be relative to the inherited data context. Have you tried binding to a view model property to ComboBox.Text?
UPDATE
Sorry, misread your question. Didn't see that the user control already has its own view model.
While it seems like there's a better approach, you could expose dependency properties on the user control that exposé the text of each combobox. Just thinking out loud.
The only clean way to do this is with binding, and the only way that would be recommended is if the user control exposes a DependencyProperty for the ViewModel or the individual text properties (as was suggested by sellmeadog) for consumption. Then you can have a property in the parent ViewModel that binds directly to that Dependency Property.
I have an application that uses Caliburn.Micro. My View contains a user control which contains e.g. a tab control. I want to be able to access that tab control from the outer ViewModel to select a particular tab. Is it possible?
Thanks.
The standard MVVM way is to have the TabControls SelectedItem property bound to a property on your viewModel.
<TabControl ItemsSource="{Binding PropertyToYourViews}"
SelectedItem="{Binding PropertyToYourSelectedView}">
</TabControl>
If you do it this way your ViewModel does not have to know about the existence of the TabControl.
The next step is dependant on your implementation. Your outer ViewModel could simply keep a reference to the child viewModels SelectedView property and access it directly however,
If you want to keep your ViewModels decoupled then you will need to implement some sort of notification system. I'm not sure of the specifics of Caliburn.Micro but most MVVM frameworks offer some kind of solution for this.
Implementation would depend on exactly how you have it set up, but you can bind a variable in your view model to the SelectedItem of the TabControl
I have a tab control where each TabItem is a UserControl. I'd like to hold the UserControls in the TabControl's ItemsSource. Does ItemsSource list go in the Window's ViewModel? If so, I feel like it's breaking MVVM since the ViewModel would now have GUI controls within it. Or do I put this list in the codebehind of the window that holds the tab control?
Any suggestions would be great!
With tab controls, more often than not the individual tabs are created statically in XAML rather than at run time by data binding. However there is no reason why you shouldn't do this. If you have a collection of views, they should definitely be stored in a view.
Bear in mind that you could also bind the ItemsSource to a list of ViewModels objects and WPF will generate a view for you with the ItemTemplate, with the ViewModel object set as the DataContext. This collection of ViewModels should be stored in a view model, although at some point a view model will obviously have to be stored in a view.
This can most likely be done in a number of ways, all which are up for debate on how "MVVM-friendly" they are.
My setup looks like the following.
My Main window has a DataContext bound to a MainWindowViewModel which contains an property
public ObservableCollection<Workspace> WorkspaceCollection{get;set;}
MainWindow has a TabControl which ItemsSource is bound to WorkspaceCollection
Workspace are all viewmodels and are bound to different views/usercontrols via DataTemplates
You might have a look at the Write sample application of the WPF Application Framework (WAF). It has a TabControl where each TabItem is a UserControl and it does this by applying the MVVM pattern.
Here's what I've done.
I created an interface that all of my controls implement, IMyAppControl, which has some information such as Title, Description, other metadata.
My Main Window has an ObservableCollection that the tab ItemsSource binds to.
I have a dependency property on my ViewModel which is the DataContext for my View. The ViewModel has no reference to the View. The property on the ViewModel is going to reference a control on the view, but I need to be able to set this property in XAML.
How is this possible? One thought I had was to develop a custom control which has a Property property and a Value property, so you could do something like this in the View to set the property on the ViewModel:
<PropertySetter Property="{Binding MyViewModelDependencyProperty}" Value="{Binding ElementName=aControlOnMyView" />
Before I went down this route, I wanted to check if there was any other approach I could take?
Thanks for the detailed reply Ray, but if I give you a bit more detail about the problem I'm trying to solve, you might get a better idea of why I mentioned the approach I did.
Basically, what I'm trying to do is set the focus to a textbox when the user hits a button. I've written an attached property which you can attach to the Button control, specify what the trigger event is (in this case the 'Click' event), and then what control to focus on. This works really nicely, and keeps everything in XAML.
However, I now have a use case where the focus should be set to an arbitrary text box from the click event on a button which is part of a toolbar. This toolbar is itself a user control which is sitting inside another user control, which is inside another user control! This toolbar needs to be reusable across various different forms, and each time, the control to set focus on after you click the button will be different per form.
That's why I had the idea of making the focus control (i.e. a textbox) a property on the view model itself (on my ViewModel base to be precise), and have the ViewModel base code (which the toolbar is bound to), set the focus to the control when the button is clicked (and the e.g. Add/Edit method is called on the ViewModel base).
In unit test land, the control to focus on property will be null, so it's .Focus() method just won't be called. So I can't see an issue there. My problem is then how you set the focus control property from XAML, which is why I had the PropertySetter idea.
I don't like the fact that the ViewModel has any reference to controls sitting on the view, but I can't see another way to achieve what I need. What if the logic that dictates whether to set focus to the control is quite complex? This would sit in the ViewModel surely? Therefore, is there any harm in the ViewModel having this UIElement property? It still knows nothing about the specific View it is bound to, it just knows that there is a control which it needs to set focus to when some action happens on the ViewModel.
My first reaction (and it's a strong one) is so say "Don't do that!" By giving your view model a reference to a part of your UI you are breaking the encapsulation that makes view models so powerful and useful.
For example, what if you want to unit test your view model or serialize it to disk? In each case the piece of your UI will not be present, because there will be no view at all. Your tests will miss coverage and your reconstitution will be incomplete.
If your view model actually needs references to UI objects and there is no better way to architect it, the best solution is to have the view model itself construct those controls it requires a reference to. Then your view can incorporate that control as the Content of a ContentPresenter via binding and provide a Style to configure the control, including a ControlTemplate to provide its content. Thusly:
public class MyViewModel
{
public ListBox SpecialControl { get; set; }
public MyViewModel()
{
SpecialControl = new ListBox();
}
}
and
<DataTemplate TargetType="{x:Type local:MyViewModel}">
<DataTemplate.Resources>
<Style TargetType="ListBox" ... />
</DataTemplate.Resources>
...
<ContentPresenter Content="{Binding SpecialControl}" />
</DataTemplate>
Other possibilities are:
Have the view model actually derive from the Control class, then override OnApplyTemplate() and use GetTemplateChild to find a template item whose name starts with "PART_"
Implement an attached property that takes a property name, finds that property in the DataContext, and sets it to the DependencyObject to which the property is attached.
Implement your PropertySetter idea
My option #2 would look like this:
<DataTemplate TargetType="{x:Type MyViewModel}">
...
<TextBox local:PropertyHelper.SetViewModelToThis="SpecialControl" />
...
</DataTemplate>
The code in the SetViewModelToThis PropertyChangedCallback would get the view model from the DataContext, reflect on it to find the "SpecialControl" property, then set it to the TextBox. Note that the implementation of SetViewModelToThis must take into account the possiblity that DataContext is not set right away, and that it maybe changed requiring the old setting to be removed and a new one made.
First of all, the DataContext of the control should be the ViewModel object and not a property of it. Second, when you TwoWay bind a property of ViewModel to your control, changes in the control's value will update (in your case, 'set') the value of ViewModel's property.