I'm trying to access the view model of a view in code-behind using the DataContext property. However, no matter at what point in the view lifecycle I'm trying to access it, the property is always null.
Simple dummy setup:
// shell
internal class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
public ShellViewModel() {
ActivateItem(new MyTestViewModel());
}
}
// view code-behind
public partial class MyTestView : UserControl {
public MyTestView() {
InitializeComponent();
}
protected override void OnInitialized(EventArgs args) {
var vm = DataContext as MyTestViewModel;
Debug.Assert(vm != null); // is always null!
}
}
The view get's correctly initialized, the view model is being called and both can be bound together. When I bind a property of the view model to a view control, it can be accessed without problems. Only during initialization, the DataContext of the view is always null. What do?
MVVM framework: Caliburn.Micro
public partial class MyTestView : UserControl {
public MyTestView() {
InitializeComponent();
}
public MyTestViewModel ViewModel() {
return (MyTestViewModel)Datacontext;
}
}
Doing this for whatever reason sort of breaks the idea behind the pattern since the viewmodel shouldn't be hardcoded into the view. Understandable for testing purposes but for any actual use; bad form.
--Edit -- I was thinking view only actions, but for some reason it was late and wasn't thinking about accessing data from within the view not accessing view from within ViewModel.
This should get what you need. Only after the Framework binds the view with the viewmodel (i.e. datacontext is set, once Loaded has been hit) will this work. If you need to do something beforehand I am not sure what else would be a better solution.
Related
So I currently have a Window with a TabControl. The MainWindow has its own ViewModel and all the TabItems have their own ViewModels also.
I can easily change tabs from the MainWindow ViewModel through a bound SelectedIndex property. What I would like to do is change to another tab from code that runs within ANOTHER tab viewmodel. Since the Tabs are not part of the MainWindowViewModel, I am looking for a clean way to change the Tab without resorting to code behind to do it.
There are also cases, where I might need to change the tab from something such as a message prompt. I thinking my only way is to create and event and subscribe to that from MainWindowViewModel.
So I solved this with an EventAggregator.
public static class IntAggregator
{
public static void Transmit(int data)
{
if (OnDataTransmitted != null)
{
OnDataTransmitted(data);
}
}
public static Action<int> OnDataTransmitted;
}
First ViewModel sends data.
public class ModifyUsersViewModel : INotifyPropertyChanged
{
private void change_tab(int data)
{
IntAggregator.Transmit(data);
}
}
Second ViewModel receives data and then does something with it.
public class MainWindowViewModel : INotifyPropertyChanged
{
private int _Tab_SelectedIndex = 0;
public int Tab_SelectedIndex
{
get
{
return _Tab_SelectedIndex;
}
set
{
_Tab_SelectedIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs("Tab_SelectedIndex"));
}
}
public MainWindowViewModel()
{
IntAggregator.OnDataTransmitted += OnDataReceived;
}
private void OnDataReceived(int data)
{
Tab_SelectedIndex = data;
}
}
Rather than trying to bind to SelectedIndex, if the TabItems have their own view models, then you can create a property for each of those view models: IsSelected and then bind the TabItem.IsSelected property to that:
<TabItem IsSelected="{Binding IsSelected}">
This prevents the view models from needing to know the index of their corresponding TabItem, something I would argue is a detail that should be specific to the view and something the view model should not concern itself with. What if you add another TabItem or want to change the order? Now you've got changes to make in the view models for something that could be just simple change to the view.
I'm trying to learn on MVVM. I've understand the concept, however, i'm confused about the binding. I'm not sure where to bind my Fill property. Please help. Tqvm in advanced.
View - name: MainScreen.xaml
<Path Fill="{Binding mainScreenClass, Converter={StaticResource colorConverter}}"/>
inCodeBehind
DataContext = new vmMainScreen();
ViewModel - name:vmMainScreen
public ICommand cmdMouseEnterNav { get; private set; }
public mMainScreen mainScreenClass { get; set; }
public vmMainScreen()
{
mainScreenClass = new mMainScreen();
mainScreenClass.propNaviconFill = new SolidColorBrush(Colors.White);
naviconMouseEventChecker();
}
private void naviconMouseEventChecker()
{
cmdMouseEnterNav = new SimpleCommand
{
ExecuteDelegate = x => mainScreenClass.propNaviconFill = (SolidColorBrush)(new BrushConverter().ConvertFrom("#c5a02b"))
};
}
Model - name:mMainScreen
public class mMainScreen : INotifyPropertyChanged
{
private Brush _NaviconFill = new SolidColorBrush(Colors.White);
public Brush propNaviconFill
{
get
{
return this._NaviconFill;
}
set
{
this._NaviconFill = value;
NotifyPropertyChanged("propNaviconFill");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
I understand that when i breakpoint on my colorConverter, I'm getting the class. Not the property of propNaviconFill. If i create another property with the Brush class on my ViewModel and bind it to Fill, there is no problem. But that means I'm not following the correct structure of MVVM. Thanks again.
You should bind to the property of your view model.
<Path Fill="{Binding propNaviconFill, Converter={StaticResource colorConverter}}"/>
Use the view model implementing the INotifyPropertyChanged as the data context of your view.
DataContext = new mMainScreen();
If you really want to use vmMainScreen as your data context, then vmMainScreen should implement INotifyPropertyChanged there and you should study how NotifyPropertyChanged was used to notify the view that the view model property has changed.
Keep in mind there are two basic types of MVVM:
1. View First
2. View Model First
Based on your example you are trying to do View First. This is easier to implement but comes with drawbacks on larger projects since the view controls the creation of the ViewModel it's harder to inject data or state into the ViewModel.
For all MVVM patterns you have the three parts:
Model - Basically a state bag. This thing is just like a customer class which most of the time implements INotifyProperty changed.
ViewModel - This is like the controller class in MVC. It has all the real logic and does the work.
View - This is your XAML and it only holds presentation logic. The code-behind class ie: MyWindow.xaml.cs should not be used except to setup the ViewModel if your going View First. (there are exceptions of course but generally it should basically be empty)
For View First
Your Window (or control) should create the ViewModel in the constructor and assign it to the DataContext.
Your ViewModel will have ICommand's, ObservableCollections and such that can be bound to controls in the View. So when your constructor fires you fill up your data and put it into the necessary structures; because of databinding this gets related to the View and shown.
Your Model (you usually have more than one, can have Customer, Order, StockTicker or whatever.) These are created by the ViewModel and put into things such as ObservableCollections for the View to databind to.
I have a userControl named SensorControl which I want to bind its dataContext to a viewModel class named SensorModel.
The binding is successful and the initial values of viewModel is visible in the userControl, but the problem is that when I change the viewModel's properties, the userControl does not update, and I have to manually update that (by assigning null to its dataContext and assigning the viewModel again).
Here is a simplified example of my code.
SensorControl.xml:
<UserControl ...[other attributes]... DataContext={Binding Model}>
.
.
.
<Label Content="{Binding Title}"/>
.
.
.
</UserControl>
SensorControl.xml.cs (code-behind):
public partial class SensorControl : UserControl
{
public SensorModel model;
public SensorModel Model
{
get { return model; }
set { model = value; }
}
public SensorControl(SensorModel sm)
{
Model = sm;
}
}
MainWindow.xml.cs:
public partial class MainWindow : Window
{
public SensorModel sensorModel_1;
public SensorControl sensorControl_1;
public MainWindow()
{
InitializeComponent();
sensorModel_1 = new SensorModel(...[some arguments]...);
sensorControl_1 = new SensorControl(sensorModel_1);
mainGrid.Children.Add(sensorControl_1);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
sensorModel_1.Title = "myTitle";
//The UserControl does not update
}
0) I implemented INotifyPropertyChanged in SensorModel
1) The reason I need this, is that there is only one single concept of 'Sensor' in my project (it is a real electronic sensor) and therefore I have a single model for it (that deal with the real sensor, the database, etc), but in the UI I have multiple userControls for presenting different aspects of Sensor. So I have to create one instance of model (SensorModel) for each real sensor, and multiple userControls must bind to that (each one uses different parts of model).
2) I'm not that new to WPF, but I'm kind of new to MVVM and it's possible that I misunderstand something essential, so I would appreciate if someone could clearly explain the correct approach.
Thanks in advance
In your UserControl, remove the DataContext attribute and add an x:Name attribute. Then in your Label, bind like this:
<UserControl x:Name="uc">
<Label Content="{Binding ElementName=uc,Path=Model.Title}" />
</UserControl>
I believe the issue is the DataContext can't be set to Model because binding works off the parent's DataContext which will be based on mainGrid when it gets added as a child to that. Since the property "Model" doesn't exist in maiGrid's DataContext no binding will occur so your update won't reflect. Getting the DataContext of a UserControl properly can be tricky. I use the ElementName quite a bit or create DependencyProperties on the UserControl and then set them from the parent who will be using the control.
You need to set the DataContext to your ViewModel class in your View, and if you're applying the MVVM pattern, you should use ICommand for actions. Maybe it would be better If you'd implement a MainView class that does the logic in the background instead in the MainWindow class.
public MainWindow()
{
InitializeComponent();
DataContext = new MainView();
// sensorModel_1 = new SensorModel(...[some arguments]...);
// sensorControl_1 = new SensorControl(sensorModel_1);
// mainGrid.Children.Add(sensorControl_1);
}
Your MainView class :
public class MainView {
public SensorControl Control {get; internal set;}
...
}
And in your xaml change the binding :
<Label Content="{Binding Control.Model.Title}"/>
Thanks to all of you guys, I took your advices and finally I found a way.
1) I implemented an event in the SensorModel that fires every time any of properties changes (name ModelChanged)
2) Then as Merve & manOvision both suggested, I declared a dependency property in the SensorControl (of type SensorModel) and bind ui elements to that (Binding Path=Model.Title)
3) Then I used the ModelChanged event of this dependency property in the SensorControl and raise an event (of type INotifyPropertyChanged) so the bindings update their value
It works fine.
I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}
my question is relatively easy, I guess.
I have a page to display my data. On a click on a button I want to open a new page with the datacontext of an element 2 layers above the datacontext of the current element.
Explanation:
My ViewModel is a class (ViewModelContainer) that contains more ViewModels. One is a summary of values and one is the detailed view of that.
public class SummaryViewModel
{
public int somevalue; // is a property
public ObservableCollection<SummarizedItems> items; // is a property
}
public class DetailsViewModel
{
public int someOthervalue; // is a property
public int stuffA; // is a property
public int stuffB; // is a property
}
public class ViewModelContainer : ViewModelBase
{
private SummaryViewModel _sum;
public SummaryViewModel sum { } // is a property
private DetailsViewModel _det;
public DetailsViewModel det { } // is a property
}
The View where I can press a button is bound to the value of the ObservableCollection of SummaryViewModel.
Everything is fine till now. When I press the button, a new page, showing the details should be opened. I use an ICommand to handle the click, and pass it the details view as a CommandParameter.
<Button Name="OpenDetailsButton" Command="{Binding Path=ACommand}" CommandParameter="{DynamicResource Details}"
I define a page as a resource in the same file, where the datacontext is still ViewModelContainer.
<pages:DetailsViewPage DataContext="{Binding Path=det }" x:Key="Details"/>
The page opens, but the datacontext is not available. I get the following error:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext.
Has anyone an idea how I could open the details view and providing the datacontext? I cannot move the DetailsViewModel to another class, because it is only possible to update it there.
Thanks
I have solved the problem by creating a helper to go up the visual tree and use the datacontext of the element i needed. Thanks for everybody who tried to help :)
the method looks like this:
public static UIELEMENT FindUiElementUpVisualTree(DependencyObject initial)
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(UIELEMENT))
{
current = VisualTreeHelper.GetParent(current);
}
return current as UIELEMENT;
}
where UIELEMENT is the object you are looking for e.g. a window or a button or something.
usually views and viewmodels have a one to one relationship. In this case it seems like there is a many to one relationship. How about a DetailsPageViewModel?