Put a View Control in its ViewModel - c#

I'm developing a Windows 8.1 Store app with C# and .Net Framework 4.5.1.
I'm trying to bind Password.SecurePassword to a ViewModel, and reading this SO answer I found a way to do it: Put the PasswordBox in my ViewModel.
But I don't know how to do it. I know how to bind Dependency Properties, but I don't know how to put that control on my ViewModel. This is my XAML:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<PasswordBox x:Name="userPassword" />
</Grid>
</Page>
What do I have to do?

You have several options but I'll just give you the basic option without third party libraries.
In your Page constructor. You can do something like this.
public Page()
{
var mainViewModel = this.DataContext as MainViewModel;
if(mainViewModel != null)
{
mainViewModel.PasswordBox = userPassword;
}
}
You can also set it on the Loaded event of the View and set the PasswordBox to the ViewModel.

Related

Rider Code Completion for Autowired ViewModel (Prism)

I'm working on a project that uses Prism for its client software. I have a UserControl XAML file that looks something like this:
<UserControl x:Class="UserModule.Frontend.UserListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ListView ItemsSource="{Binding Users}"/>
</Grid>
</UserControl>
The actual XAML is a bit longer, but my question is:
Rider shows me a warning at the binding of the list view's ItemsSource property. I have a ViewModel that Prism injects correctly and I can see that the list has been populated. However, at design time, I can't see if the property exists, if I don't check for myself. At the same time, I get a warning in the ViewModel class, that the public getter of Users could be removed.
Is there a way to get code completion to recognize the autowired ViewModel with Prism?
You need to define the d:DataContext for your view. And while you're at it, vote for the feature...

WPF XAML: Difference between DataContext as attribute or property for XAML element?

I'm currently getting started with XAML and I have a question regarding how to define the DataContext of an element.
I've created a View that includes a Page with the following markup:
<Page x:Class="View.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ViewModel="clr-namespace:ViewModel"
mc:Ignorable="d"
Title="MainView">
...
</Page>
When I want to give this Page a DataContext to be used by child elements, this works:
<Page x:Class="View.MainView"
...
mc:Ignorable="d"
Title="MainView">
<Page.DataContext>
<ViewModel:MainViewModel />
</Page.DataContext>
...
</Page>
And this doesn't:
<Page x:Class="View.MainView"
...
mc:Ignorable="d"
Title="MainView" DataContext="ViewModel:MainViewModel">
...
</Page>
For me, it looks like the Page element expects the DataSource to be defined as a XAML property and not as an attribute. However, the IntelliSense in Visual Studio offers me a DataContext attribute for the Page, so I guess I'm just using a wrong syntax here. Can you point that out to me?
Thanks!
You can use the attribute to specify the DataContext, but you should consider how does your viewmodel get instantiated.
Using a property in this way
<Page.DataContext>
<ViewModel:MainViewModel />
</Page.DataContext>
you tell WPF to instantiate the MainViewModel and to assign the created object to the DataContext property of the Page.
With an attribute, you just specify a string in that case:
DataContext="ViewModel:MainViewModel"
But you want WPF to create an instance for you.
So you can use e.g. a Binding or a StaticResource / DynamicResource to assign a created instance to the DataContext property:
DataContext="{Binding ViewModel}"
or
<Page DataContext="{StaticResource ViewModel}">
<Page.Resources>
<ViewModel:MainViewModel x:Key = "ViewModel"/>
</Page.Resources>
</Page>

WPF Communication between User Controls

I'm trying to find the best way to communicate between two User Controls. I have a main XAML window which contains two User Controls which in turn contain various controls. The Code behind of each User Control simply sets the DataContext to a View Model that is associated to it. The View Model contains objects that are bound to the controls.
What I'd like to do is capture when a list box in User Control 1 changes selection, the new selected item be displayed in an edit box in User Control 2. As I'm using View Models I can't declare Dependency Properties so I was wondering what is the accepted way to perform this?
I've attached some basic code to show how I'm setting the controls.
Main Window XAML
<Window x:Class="CommsTest.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CommsTest.View"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 />
<local:UserControl2 />
</Grid>
UserControl1 XAML
<UserControl x:Class="CommsTest.View.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="50,110,0,0" Name="comboBox1" VerticalAlignment="Top" Width="199" ItemsSource="{Binding Combo1}" />
</Grid>
UserControl1ViewModel.cs
class UserControl1ViewModel
{
private ObservableCollection<string> combo1 = new ObservableCollection<string>();
public ObservableCollection<string> Combo1
{
get { return combo1; }
}
}
UserControl2.XAML
<UserControl x:Class="CommsTest.View.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="63,84,0,0" Name="textBox1" VerticalAlignment="Top" Width="170" Text="{Binding Path=Text1}" />
</Grid>
UserControl2ViewModel.cs
class UserControl2ViewModel
{
private string text1;
public string Text1
{
get { return text1; }
set { text1 = value; }
}
}
How do I get UserControl2.Text1 to be the selected value of UserControl2.Combo1?
Thanks
While I understand that you are asking how to communicate between UserControls, I would suggest that the answer is to communicate between the view models. This can be easily achieved using delegate objects. In general, you'd need to have a parent view model that is common to the two child view models.
I recently answered a similar question, so I won't duplicate answers. Instead, I would ask you to take a look at the answer from the Passing parameters between viewmodels post here on StackOverflow which explains the solution with code examples.
UPDATE >>>
When I said that you need a common parent to your child view models, I don't mean anything to do with inheritance. I just mean that the parent holds a variable instance of each of the child view models... the parent instantiates the child view models.
Instead of creating the view model instance in the view code behind, you can do it in the parent view model and connect the view models to the views like this:
In Resources:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:UsersViewModel}">
<Views:UsersView />
</DataTemplate>
Then you just need to display an instance of the view model and the appropriate view will be displayed:
In ParentView:
<ContentControl Content="{Binding ViewModel}" />
In ParentViewModel:
public BaseViewModel ViewModel { get; set; } // Implement INotifyPropertyChanged
Then when you want to display a new view:
ViewModel = new UsersViewModel();
If your child views do not have a BaseViewModel and/or are not interchangable, then you could just add a property for each of them:
public MainViewmodel MainViewModel { get; set; } // Implement INotifyPropertyChanged
public UsersViewmodel UsersViewModel { get; set; } // properly for these properties
Either way, you'll need access to these view models from the parent view if you are going to be able to 'connect them together' with handlers.
I would suggest you, to have only one ViewModel and bind the DataContext to MainWindow.xaml, instead of doing it to each UserControl.
You should also implement INotifyPropertyChanged in your ViewModel to notify the UI whenever you change the value from the code or ViewModel.
Maybe you should think about your self-imposed restriction of not having dependency properties in user controls. MVVM is nice for the overal architecture, but you can overdo it if you put it into every class and control you plan to do.
If your user controls are just controls for the user, they should behave as such. I have never had to communicate with a TextBoxViewModel or ButtonViewModel, those are controls I simply use. Maybe yours is simple, too and does not need it's own viewmodel. Then you could communicate by using dependency properties as all other controls do.

View is not being resolved with Toolbar ItemsSource

It doesn't seem like the Caliburn Micro framework is retrieving my SinglePaintToolbarView when it is binded as a list of buttons in the toolbar of the ShellView. I would like the buttons to just display their text content when they are added to the toolbar. But, instead I'm getting this:
There doesn't appear to be any clickable buttons in the toolbar. I know my plugins are being loaded successfully, because I was able to bind one of the plugins in the list as a ContentControl and the view appeared. It just doesn't seem to work when I try to bind a list of the plugins in a toolbar.
Here is what I have:
ShellView.xaml
<UserControl x:Class="Starbolt.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ToolBarTray>
<ToolBar ItemsSource="{Binding Path=ToolbarPlugins}"/>
</ToolBarTray>
</Grid>
</UserControl>
ShellViewModel.cs
[Export(typeof(IShell))]
public class ShellViewModel : PropertyChangedBase, IShell
{
[ImportMany(typeof(IToolbarPlugin))]
private IEnumerable<IToolbarPlugin> _toolbarPlugins = null;
public IEnumerable<IToolbarPlugin> ToolbarPlugins { get { return _toolbarPlugins; } }
}
SinglePaintToolbarView.xaml
<UserControl x:Class="Starbolt.Plugin.SinglePaintTool.Views.SinglePaintToolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="128" d:DesignWidth="32">
<Button Name="btnSinglePaintTool" Content="Single Paint Tool" Width="128" Height="32"/>
</UserControl>
SinglePaintToolViewModel.cs
[Export(typeof(IToolbarPlugin))]
public class SinglePaintToolViewModel : IToolbarPlugin
{
}
Basically, your design seems to be working. If you replace
<ToolBarTray>
<ToolBar x:Name="ToolbarPlugins"/>
</ToolBarTray>
(note that you do not need to bind the ItemsSource explicitly, you can just as well use the Caliburn Micro property name conventions) with the following:
<ListBox x:Name="ToolbarPlugins"/>
the SinglePaintToolView button is displayed as intended.
I suspect that the problem is with the ToolBar ControlTemplate, which most certainly restricts the toolbar items layout more than what for example a ListBox ControlTemplate does.
So my guess is that if you really want to use the ToolBar control to display your IToolbarPlugin views, you will probably have to design a dedicated ToolBar control template in your project.
Alternatively, you could implement a toolbar replacement using e.g. ListBox. This could be a start:
<ListBox x:Name="ToolbarPlugins">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

Setting the DataContext in "MainView" filters down to all "ChildView's"

I would like to know if this is a standard feature of .NET: when setting the DataContext in the ParentView, it filters down to all child views.
Say you have ParentView, ChildView1 and ChildView2:
<UserControl x:Class="DXWPFApplication1.ParentView"
xmlns:view="clr-namespace:DXWPFApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<view:ChildView1 x:Name="childView1"/>
</Grid>
</UserControl>
<UserControl x:Class="DXWPFApplication1.ChildView1"
xmlns:view="clr-namespace:DXWPFApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<view:ChildView2 x:Name="childView2"/>
</Grid>
</UserControl>
Code behind of ParentView:
public ParentView()
{
InitializeComponent();
DataContext = "ViewModel"; //BreakPoint here
//
//When the first DataContext is set, all the DataContext's below are set as well
//
childView1.DataContext = DataContext;
childView1.childView2.DataContext = DataContext;
}
NOTE: Breakpoint when setting first DataContext
Why are all the DataContexts set when I have only set the ParentView's DataContext?
What can I do to prevent this from happening?
This is standard behaviour, and normally desired. To prevent it, set DataContext to {x:Null} in your markup
A component in the visual tree inherts the data context from its parent. Your child view resides in the visual tree of the parent view so it will get the parent context assigned. You need to explicitly set it to something different if you want to change it (either inside the child view constructor or in the xaml, for example <view:ChildView2 DataContext="{x:Null}" x:Name="childView2"/>).
Why was that done: Because it is almost always what you want.
Willem if you are using separate ViewModels for each View, what you should do is nest your ViewModels.
So say you have MainViewModel with properties exposed ChildViewModel1 and ChildViewModel2. Then you would set the binding of the child usercontrols to
DataContext="{Binding ChildViewModel1}" and
DataContext="{Binding ChildViewModel2}" respectively
all the while the main view maintains its DataContext as MainViewModel

Categories