C# WPF MVVM XAML: Using ContentPresenter Style to display ViewModel - c#

So I have a series of pages that I want to display for an application I am working on but I want to start by simply displaying a ViewModel with a ContentPresenter within another ViewModel. I can make it work if I use:
<ContentPresenter>
<ContentPresenter.Content>
<connection:ConnectionSelectPage />
</ContentPresenter.Content>
</ContentPresenter>
I want to make it more advanced using Styles because I will need to be able to switch which Viewmodel is displayed based on a DataTrigger. I have come up with this as a start before delving into the DataTriggers and multiple ViewModels which I want to simply perform the exact same function as the above code:
<ContentPresenter>
<ContentPresenter.Resources>
<Style x:Key="ConnectPage">
<Setter Property="ContentPresenter.Content">
<Setter.Value>
<connection:ConnectionSelectPage />
</Setter.Value>
</Setter>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
This new code does not display anything in the application and I find that confusing because from what I thought I knew about XAML and WPF, these two blocks of code should be identical. Am I missing something?

Just set your content presenters Content binding to the page property in your main view model...
<ContentPresenter Content='{Binding CurrentPage}' />
...where CurrentPage is of type 'object' or, better yet, some base class you're using for all your page view models. Then you just use data templates to dictate how the ContentPresenter should be populated for each of your page types:
<DataTemplate DataType="{x:Type Page1ViewModel}">
<views:Page1UserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type Page2ViewModel}">
<views:Page2UserControl />
</DataTemplate>
... etc ...
So long as the CurrentPage property supports property change notification the child views will automatically change whenever you change its value.

Related

Why doesn't my style get set on the child of a ContentControl in WPF

I have the following XAML code for WPF
<ContentControl>
<ContentControl.Resources>
<Style TargetType="selections:EntitySelector">
<Setter
Property="EntitySelectorManager"
Value="{Binding SelectorManager, Mode=OneWay }"/>
</Style>
</ContentControl.Resources>
<ContentControl.Content>
<Binding Path="Editor" />
</ContentControl.Content>
</ContentControl>
Then in code behind in response to some event I have set the Editor propety
this.Editor = element
where element is a control that contains one or more EntitySelector objects. However once the control is instantiated in the visual tree I can see that the binding has not worked.
First I check the SelectorManager property on the DataContext at the level of the ContentControl. This seems in order
Now I go into the ContentControl and see if any of the EntitySelector controls have their EntitySelectorManager properties set.
You can see that there is a binding expression but the result is Null. Why is this?
I have a solution but I don't really like it. Using a dynamic resource that is initialized from codebehind
public WeinCamWindow(WeinCamWorkPiece camViewModel)
{
ViewModel = camViewModel;
InitializeComponent();
DataContext = ViewModel;
this.Resources["EntitySelectionManager"] = ViewModel.SelectorManager;
}
and in the style use a dynamic resource
<Style TargetType="selections:EntitySelector">
<!-- 'EntitySelectionManager' is set in code behind. -->
<Setter Property="EntitySelectorManager"
Value="{DynamicResource EntitySelectionManager}" />
</Style>
The dyamic resource propogates where all the other tricks I tried didn't work.

WPF ContentPresenter Content null when Visibility=Collapsed

I'm making custom control with edit/view state.
I've made 2 dependencyProperties with default styles:
<Setter Property="EditContent">
<Setter.Value>
<TextBox Text="{Binding ElementName=parent, Path=LocalValue}" />
</Setter.Value>
</Setter>
<Setter Property="ViewContent">
<Setter.Value>
<TextBox IsEnabled="False" Text="{Binding ElementName=parent, Path=LocalValue}" />
</Setter.Value>
</Setter>
and then, displaying these Contents depending on IsReadOnly value like this:
<Border Background="Transparent"
MouseLeftButtonDown="UIElement_OnMouseLeftButtonDown"
Visibility="{Binding ElementName=parent,
Path=IsReadOnly,
Converter={StaticResource BooleanToCollapsingVisibilityConverter},
ConverterParameter=true}">
<ContentPresenter Content="{Binding ElementName=parent, Path=ViewContent}" />
</Border>
Problem is, that when my control loads with IsReadOnly = true, Content Property of my ContentPresenter for EditContent is null.
When I'm changing IsReadOnly to false Content of EditContent loads, but my binding does not work (like it's not evaluated).
How to re-evaluate bindings in WPF, or force ContentPresenter to load it's content on created (even if it's invisible)?
P.S. If I navigate to this ContentPresenter in Snoop (or WPF Inspector) when It's invisible - it's empty. When I navigate to it when it's visible - bindings starting to work
Please, have a look at output windows while debugging. you will see errormessage describing the binding problem. wpf rule nr.1: always check output window.
The reason is that your edit / view content has different NameScope, therefore ElementName does not work. However, in your Control you can set NameScope manually, by using something like:
var currentScope = NameScope.GetNameScope(this);
NameScope.SetNameScope((UIElement)this.EditContent, currentScope)
in your case you are using styles and styles has its own namescope, so it won't work. Imagine, that you used the style on multiple pages. What element should be used?
Sometimes you can use Source={x:Reference elementName}, but you cannot use it in direct children of the source the element, because the element does not exist yet, when the {x:Reference } is being resolved
never set content-like properties inside styles. if you applied your style to more than one element, that the same TextBox from ViewContent would be added to visual tree multiple times and that throws an exception. You should use DataTemplates instead of direct content in styles

Passing Non-Item Values to Properties in ItemTemplate

Today I'm having trouble passing values from a parent control down to the properties of a child control in a list.
I have a custom control which I've made which functions as a Thumbnail Check Box. Essentially it's just a checkbox wrapped around an image with some nice borders. It's all wrapped up into a DLL and deployed as a custom control
If I want to use a single instance of the control, I can do so like this...
<tcb:ThumbnailCheckBox IsChecked="True"
ImagePath="D:\Pictures\123.jpg"
CornerRadius="10"
Height="{Binding ThumbnailSize}"
Margin="10" />
Code Listing 1 - Single Use
This works great, and easily binds to ThumbnailSize on my ViewModel so I can change the size of the image in the control however I want.
The problem is when I want to expand the use of this control into a list, I'm running into a few problems.
To begin, I've styled the ListBox control to meet my needs like so...
<Style TargetType="{x:Type ListBox}"
x:Key="WrappingImageListBox">
<!-- Set the ItemTemplate of the ListBox to a DataTemplate
which explains how to display an object of type BitmapImage. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{TemplateBinding utilities:MyAttachedProperties.ImageSize}"
CornerRadius="8"
Margin="10">
</tcb:ThumbnailCheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
<!-- Swap out the default items panel with a WrapPanel so that
the images will be arranged with a different layout. -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Set this attached property to 'Disabled' so that the
ScrollViewer in the ListBox will never show a horizontal
scrollbar, and the WrapPanel it contains will be constrained
to the width of the ScrollViewer's viewable surface. -->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
</Style>
Code Listing 2 - ListBox Style
And I call it like this from my main view...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent"
util:MyAttachedProperties.ImageSize="500"/>
Code Listing 3 - Main Call
This works exactly as I'd like, except for the ImageSize property. Both ImagePath and Selected are properties of the individual list items being bound to the ListBox.
As you can see, I created an attached property to try to pass the value (500), but it doesn't seem to be working. I should note that I think the style I've created is correct because the elements use the default value.
public static class MyAttachedProperties
{
public static double GetImageSize(DependencyObject obj)
{
return (double)obj.GetValue(ImageSizeProperty);
}
public static void SetImageSize(DependencyObject obj, double value)
{
obj.SetValue(ImageSizeProperty, value);
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.RegisterAttached(
"ImageSize",
typeof(double),
typeof(MyAttachedProperties),
new FrameworkPropertyMetadata(50D));
}
Code Listing 4 - Attached Property
The 50D specified on the last line is applying to the listed control. If I change it, and recompile, the end result changes. But the sent value of 500 I specified in my ListBox Main call (listing 3) is not ever sent. Of course, I would eventually like to change the 500 into a bound property on my view model, but I won't do that until I get it working with an explicit value.
Can someone help me figure out how to send a value from my main ListBox call (listing 3) and apply it to the individual items that are populated by the template? The other properties I have work, but they are a properties of each item in the List I'm binding to the ListBox, whereas ImageSize is not.
EDIT To address First Response
This seems to be working, but it's kind of peculiar. My listbox is now being called like so...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent" />
And I've changed my style to the code you suggested...
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{Binding Path=DataContext.ThumbnailSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"
CornerRadius="8"
Margin="10">
My only concern is, now the style is accessing the ViewModel for that control directly rather than receiving a bound value.
Suppose I wanted to use the ListBox again, but on another UserControl whose ViewModel didn't have ThumbnailSize property, but used one by another name?
You see where I'm going with this... the current solution is not very extensible and is limited to the current classes as they are named exactly.
In fact, in a perfect world, I'd like to have variable names for the ImagePath and Selected properties, but that's a different discussion.
It's possible to use FindAncestor. The idea of that is, child traverses through logical tree, and tries to find parent with concrete type (in this case, ListBox), and then accesses attached property. See http://wpftutorial.net/BindingExpressions.html for more binding expressions.
In your ItemTemplate, this is how you could access ThumbnailSize property:
{Binding Path=(util:MyAttachedProperties.ImageSize),
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}}}
Essentially, the question asked here was a little bit opposite, but results are same. "How could items in ListBox access ListBox (attached) properties.

WPF: Switching Views with DataTemplates - How to have view change its DataContext automatically?

Thanks in advance for any help I can get! I'll jump right in!
Assume I have the following XAML. I left out parts that wouldn't be needed to hopefully keep this easier to read.
The TreeView is populated in the MainWindow code behind by created an ObservableCollection of MainViewModelBaseobjects. The Properties of those objects are based on a XML file read in at start by the main windows code being.
This makes the items of the TreeView of type MainViewModelBase. From there I want to use the string "Type" property (which was read in from the XML) of the SelectedItem of the Treeview to display a UserControl on the right side of the screen. Based on TONs of googling, the below code uses DataTemplates to accomplish the view switches.
My question is this. The UserControl that I want to populate needs to be be binded to another XML file, and the name of that XML file will be based on the string called Name stored in the MainViewModelBase. The code below gets the new view to show up, but I can't figure out how to get the new Views DataContext to be set to the XML. There must be some way though the content control to do this. I think that when you use a DataTemplate to do the switch, the resulting view inherits the DataContext of the ViewModel type that "created" it. But I think the DataContext isn't set until after the constructor of the view is done. Thus I can't have the constructor of the view open the XML based on the string "name" of the MainViewModel. Is there a way to have the DataContext updated after the fact? Thanks!
Another note, there is lots of items in the TreeView, thus lots of XMLs. I don't want to have all XMLs in memory at once, only when the view that needs it needs it.
Finally, I am new to WPF, so I apologize in advance if part of my above question are dumb or completely confusing. I am asking them from the perspective of someone new to the technology.
Note: I know I can bind the TreeView directly to the initial XML, and I may switch to that in the future, I'm not sure yet. Most examples online were using ObservableCollections of VeiwModel objects, so it was easier to learn this way.
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="BlankTemplate" DataType="{x:Type ViewModel:MainViewModelBase}">
<View:BlankControl/>
</DataTemplate>
<DataTemplate x:Key="ParagraphTemplate" DataType="{x:Type ViewModel:MainViewModelBase}">
<View:ParagraphControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:MainViewModelBase}">
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource BlankTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Paragraph">
<Setter Property="ContentTemplate" Value="{StaticResource ParagraphTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</Window.Resources>
<DockPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*"/>
<ColumnDefinition Width=".5*"/>
</Grid.ColumnDefinitions>
<TreeView Name="navigationPane" Grid.Column="0"/>
<ContentControl Grid.Column="1" Content="{Binding ElementName=navigationPane, Path=SelectedItem}"/>
</Grid>
After reading your post if I'm not wrong, you basically have a treeview on left hand side and on the basis of selection you want to populate some user control on right hand side. That's the crux of the requirement.
If above is the case what can be done is to bind the left and right side views to separate viewmodels. On selecting something from left hand side, you can pass the selection in the parameterized constructor of the right hand side view. Hence you'll have the input selected on LHS and can do processing on RHS
After some more Googling, I found a great project on codeproject that describes how to use the view model idea to make a load on command treeview. What I really liked about this was that it showed me how to use the INotifyPropertyChanged stuff to bind the isSelected property to something in the view model that could be used todo the XML loading etc.
I know this may seem basic to you WPF gurus out there, but this was so helpful in explaining to me how to really use the ViewModel pattern.
http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode

WPF: Data Template vs View

What's the value of using a DataTemplate to assign a ViewModel to a View? I see lot's of code that looks like this and am doing the same myself.
ViewResources.xaml
<DataTemplate DataType="{x:Type vm:GenericViewModel}">
<vw:GenericView />
</DataTemplate>
View.xaml
<ContentControl Content="{Binding Generic}" />
What are the advantages compared to displaying the View and binding to the DataContext?
View.xaml
<vw:GenericView DataContext="{Binding Generic}" />
At a minimum this appears to require less code and also plays "nicer" with the designer. I can see the need for a DataTemplate(say you're styling a TextBlock or something simple), but once you have created a View what is the point?

Categories