Using a ValueConverter after a DataContext switch - c#

I want to try out a small, custom ValueConverter in a class which after being instantiated (via default constructor, which only calls InitializeComponents() ), is given another DataContext, specifically an instance of a ViewModel.
Using a StaticResource within the Binding does not work at all (yields a NullReferenceException), since the DataContext has since then been changed (is not this anymore).
I've tried putting DataContext = this; before the InitializeComponents call, no change.
Should I be looking into this MarkupExtension gizmo (as described in this article) ?
I've also tried creating an instance of the custom Value Converter within the ViewModel (the current DataContext), doesn't help either.
I can provide additional details at all times. Thank you in advance !
I'm trying to display a ContextMenu within the TextBlock. The ContextMenu contains a sole MenuItem. The header of the MenuItem can be "Settings" for instance. The Children (rendered as MenuItems as well) of the said MenuItem stem from an Enum, hence the ItemsSource on the MenuItem.
Now all is getting displayed nicely, yet I am trying to make one of the Children (e.g. a member of the Enum) to be selected per default, since there is already a default Setting. Further background info can be found in my other question.
Edit :
...
<UserControl.Resources>
<Helpers:DisplayTypeToDefaultValueConverter x:Key="displayTypeConverter" />
</UserControl.Resources>
...
<TextBlock x:Name="InstructionLabel"
TextWrapping="Wrap" Text="{Binding Path=SelectedNodeText}"
Grid.RowSpan="1">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Settings" Name="SettingsPop"
DataContext="{Binding}"
ItemsSource="{Binding Source={StaticResource DisplayTypeValues}}"
IsCheckable="True"
Click="SettingsType_Click">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="IsChecked">
<Setter.Value>
<Binding Converter="{StaticResource displayTypeConverter}" />
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
</ContextMenu>
</TextBlock>
I've only now realized that it's the dreaded ContextMenu. That's the problem, isn't it ?

The DataContext inside the ItemContainerStyle is a member of the DisplayTypeValues collection. The only thing in the XAML you posted that will be affected by the DataContext of the UserControl changing is the InstructionLabel's text. Setting DataContext="{Binding}" as you're doing on the MenuItem is also redundant as the value will already be inherited from the parent ContextMenu.
It's not clear from your question or code what you're expecting from the DataContext or what you're trying to do with it.

Just several thoughts:
Are you sure you didn't miss setting binding path in <Binding Converter="{StaticResource displayTypeConverter}" />?
Did you check the StackTrace of the thrown exception and all it's InnerExceptions to see, whether there was something interesting?

Used an easier solution, as highlighted in my other related question.
Thank you for your input !

Related

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 - Need a combination of Tree+Grid, with Context Menu

My application is implemented by a GridView inside a TreeList.
Much to my despair, I discovered that the GridView is very primitive, compared to the widely used DataGrid. I am considering these two options:
(1) Somehow, I replace the GridView with a DataGrid (which supports Context Menu).
(2) Somehow, I add the Context Menu capability to the existent GridView.
Which of the 2 approaches (or another?) would you recommend?
Source code is much appreciated.
TIA.
Based on the linked code, here is the solution:
1 - Add the ContextMenu as a Resource:
<Window.Resources>
<ContextMenu x:Key="ItemsContextMenu" x:Shared="False">
<MenuItem>
<MenuItem.Header>
<TextBlock>
<Run>Context Menu Action for Item</Run>
<Run Text="{Binding Tag.Name}"/>
</TextBlock>
</MenuItem.Header>
</MenuItem>
</ContextMenu>
<!-- other stuff here -->
</Window.Resources>
It is recommended that you set x:Shared="False" to prevent Binding issues related to reusing the resource instance.
2 - Define an ItemContainerStyle for your TreeList that sets the ContextMenu for the TreeListItems:
<tree:TreeList ...>
<!-- other stuff here -->
<tree:TreeList.ItemContainerStyle>
<Style TargetType="{x:Type tree:TreeListItem}">
<Setter Property="ContextMenu" Value="{StaticResource ItemsContextMenu}"/>
</Style>
</tree:TreeList.ItemContainerStyle>
</tree:TreeList>
Notice that I'm using DataBinding in the ContextMenu, which means you have a proper, working DataContext in it. You should be able to use Commands and other stuff in it.

BindingExpression path errors when switching ViewModels in MVVM application

First things first, some context. If you're familiar with the problem, skip down to the BindingExpression part. This is my first major project in WPF, so I am still quite new to the MVVM pattern. Here is the only other similar question I have found, whose lacklustre answer doesn't really enthuse me much.
I have/am building a .NET 3.5 WPF application and I am using MVVM (implemented myself, no framework). Within this, I have a number of Views and ViewModels. These reside within a master ApplicationView and ApplicationViewModel respectively.
The way I change views is through using XAML DataTemplate elements in the ApplicationView, like so:
<DataTemplate DataType="{x:Type viewmodels:InitViewModel}">
<views:InitView />
</DataTemplate>
And then in the main body I have a ContentControl which binds to a property in ApplicationViewModel
<ContentControl Content="{Binding CurrentPageViewModel}"/>
When I run the application, all of this appears to work fine, and does exactly what is intended. However, when I look at the Debug output after the run, I get a lot of BindingExpression errors.
Here is one for example. I have a property, SplashText, in my InitViewModel. This is bound to a textblock in the splash screen (InitView). When the splash screen ends and I switch out the viewmodel, I get the following:
System.Windows.Data Error: 39 : BindingExpression path error: 'SplashText' property not found on 'object' ''MainMenuViewModel' (HashCode=680171)'. BindingExpression:Path=SplashText; DataItem='MainMenuViewModel' (HashCode=680171); target element is 'TextBox' (Name='FeedBackBox'); target property is 'Text' (type 'String')
I understand that this is because the bindings still exist, but the CurrentPageViewModel property of the DataContext has changed. So what I want to know is:
Is this a fleeting problem, i.e. are the views disposed of when not being used or do they (and the bad bindings) sit there in memory indefinitely?
Is there a way I can clean up or deactivate these bindings while the view is inactive?
What sort of performance knock is it going to have on my application if I leave these alone?
Is there a better way of switching views which avoids this problem?
Thanks in advance, and apologies for the monolithic question.
Edit 03/09/13 - Thanks to Jehof, Francesco De Lisi and Faster Solutions for pointing out that it is pointless to set sub-views datacontext as {Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}} because the ContentControl takes care of the datacontext.
Your specific example is not reproducible in .NET 4.5, which probably means Microsoft has fixed the problem meantime.
Nevertheless, a similar problem exists when Content and ContentTemplate are both data-bound. I am going to address that problem, which is also likely to solve problems in .NET 3.5 if anyone is still using it. For example:
<ContentControl Content="{Binding Content}" ContentTemplate="{Binding Template}" />
Or when ContentTemplate is determined by DataTrigger:
<ContentControl Content="{Binding Content}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Choice}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}" />
</DataTrigger>
<DataTrigger Binding="{Binding Choice}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
In both cases, one gets binding errors similar to those OP observed.
The trick here is to ensure that changes to Content and ContentTemplate are performed in just the right order to prevent binding errors. I've written DelayedContentControl, which ensures that Content and ContentTemplate are changed at the same time and in the right order.
<jc:DelayedContentControl Content="{Binding Content}" ContentTemplate="{Binding Template}">
Similarly for the DataTrigger case.
You can get DelayedContentControl from my opensource JungleControls library.
It looks like your DataContext goes to MainMenuViewModel while your property belongs to another ViewModel generating the error.
The CurrentPageViewModel value before and after the splash screen changes losing its Binding while switching view.
The problem is dued to DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
In fact, CurrentPageViewModel = InitViewModel when your application starts, but the problem is that every View has the same DataContext (i.e. InitViewModel at first) but I'm sure the ViewModels haven't the entire pool of needed properties to satisfy view bindings.
An example to understand:
ViewX has a binding to PropertyX, managed in ViewModelX.
ViewY has a binding to PropertyY, managed in ViewModelY.
Both have DataContext = CurrentViewModel.
On the startup CurrentViewModel = ViewModelX and both ViewX and ViewY have DataContext = ViewModelX. But this is wrong! And probably will generate an error.
What I usually do is to set in the View class the DataContext (cs or XAML if you prefer) with the corresponding View Model to be sure it fits. Then, when needed, I call a refresh method to update my values every time I switch page. If you have shared properties consider to use a Model to centralize your informations (and values).
A sample image from http://wildermuth.com/images/mvvm_layout.png
Obviously the Views are the Controls wrapped by your MainWindow.
Hope it's clear.
Lets answer your questions in sequence:
You probably already know the answer to this. When .Net garbage collects it'll remove your View object from the heap. But until this time your View object is still bound to the main DataContext on your page and will react to DataContext changed events.
The obvious thing to do is to set the Views DataContext to null. DataContext is a dependency property so the null values scope will just be your View.
As the other/lackluster answer said, it'll slow you down a bit but not a lot. I wouldn't worry too much about this.
Yes. Here's a useful thread on view navigation options: View Navigation Options
I'd also suggest looking at a framework. Something light-weight like MVVM Light will solve a bunch of problems for you with very little integration. It's ViewModelLocator pattern also does what you're doing, but without the side-effects and provides a whole bunch of cleanup options.
You can omit the binding of the DataContext in your Views
DataContext="{Binding DataContext.CurrentPageViewModel, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
cause the DataContext of your View is the DataContext of the ContentControl and that gets set by your binding of the Content-Property.
So, when your property CurrentPageViewModel is set to an InitViewModel the ContentControl will use the InitViewModel as DataContext and use the InitView as ContentTemplate and it will set his own DataContext as DataContext of the InitView.

WPF ContextMenu bind some property to another property of the same control

I have a ContextMenu and a ColumnHeaderStyle defined in Window.Resource section which I use-it to a DataGrid ColumnHeader. My code is something like this:
<ContextMenu x:Key="cm_columnHeaderMenu"/>
<Style x:Key="DefaultColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContextMenu" Value="{StaticResource cm_columnHeaderMenu}" />
</Style>
<DataGrid Grid.Column="2" Grid.Row="1" x:Name="dgridFiles" IsReadOnly="True"
ColumnHeaderStyle="{StaticResource DefaultColumnHeaderStyle}">
I want to know if I can (and if the answer it true, then HOW I can I do it) bind the ContextMenu Visibility property to same control ContextMenu Items.Count > 0 property.
Initially based on some other treeView control selections made there shoud be no items in the context menu, but i wish to add dinamically items in ContextMenu based on selection in treeView. This part is done, the context has those items. On some selections there are no-items, but still on the grid it appears an empty ContextMenu. So I believe the easiest part it would be to bind the Visibility to Items.Count property of the same control.
Sorry if my english is not good enough, I'll try to explain better if i didnt make clear 1st time.
you want to bind via RelativeSource, especially the Self mode.
I think by reading this or this you will be able to achieve your goal.
Then you'll need a binding converter to convert the integer values to the matching type and values of the Visibility property. You'll find a short tutorial here.
Regards
Using this you can bind to the property in the same control
Visibility="{Binding Path=Items.Count, RelativeSource={RelativeSource Self}}"
You also have to use a converter to achieve what you want.
Just in case you need this
Try a converter to convert the value of the item count to a boolean. So you'll end up with something like
<ContextMenu Visibility={Binding RelativeSource={RelativeSource Self},
Converter={StaticResource ItemsToVisibilityConverter}, Path=Items.Count}} />
If that doesn't work, try this with data triggers (you still need a converter anyway, and this shows a converter at work):
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/a8ad8c14-95aa-4ed4-b806-d0ae874a8d26/

Categories