UserControl Dependency Property design time - c#

I'm creating a simple User Control in WPF that contains a TextBlock inside a Button.
<UserControl x:Class="WpfExpansion.MyButton"..... >
<Grid >
<Button Background="Transparent" >
<TextBlock Text="{Binding Path=Text}"/>
</Button>
</Grid>
</UserControl>
And also the "Text" dependency property.
public partial class MyButton : UserControl
{
public MyButton()
{
InitializeComponent();
this.DataContext = this;
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyButton), new PropertyMetadata(string.Empty));
}
And then I use the UserControl like this:
<MyButton Text="Test" />
The problem is that the Visual Studio design does not change, but it works in runtime.
What is wrong?
I also tried
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Inside the UC definition, without success.

Try using FrameworkPropertyMetadata instead of PropertyMetadata, specifying AffectsRender like below, then restart Visual Studio:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyButton),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsRender));
MSDN Documentation about FrameworkPropertyMetadataOptions.AffectsRender says
Some aspect of rendering or layout composition (other than measure or
arrange) is affected by value changes to this dependency property.
For other cases, there are options like AffectsMeasure, AffectsArrange, etc.

Golden shovel candidate, still I came across the same problem and had it solved being inspired by https://www.codeproject.com/Questions/1096567/How-to-set-a-custom-dependency-property-of-user-co
Long story short: your dependency property is set on the UserControl itself and you are trying to bind the it's child property to it. The child's binding needs to have RelativeSource defined, hence the TextBlock should look like this:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=Text}" />
The only DataContext assignment needed is the one you already have in the code behind in the constructor.
UPDATE
But then I tried your attempts and came to the conclusion that if you define the DataContext in XAML already, you don't need to provide it in each of the controls. This means you need to define your UC the following way (d:DataContext=... does the trick):
<UserControl x:Class="WpfExpansion.MyButton"
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:local="clr-namespace:YRS100_Data_Analysis"
mc:Ignorable="d"
d:DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Button Background="Transparent">
<TextBlock Text="{Binding Path=Text}" />
</Button>
</Grid>
</UserControl>
Works like a charm.

Related

DataContext to UserControl [duplicate]

I have been unable to find a clean, simple, example of how to correctly implement a usercontrol with WPF that has a DependencyProperty within the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.
I am trying to:
Set the DependencyProperty from the calling ItemsControl , and
Make the value of that DependencyProperty available to the ViewModel of the called usercontrol.
I still have a lot to learn and sincerely appreciate any help.
This is the ItemsControl in the topmost usercontrol that is making the call to the InkStringView usercontrol with the DependencyProperty TextInControl (example from another question).
<ItemsControl ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the InkStringView usercontrol with the DependencyProperty.
XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
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"
x:Name="mainInkStringView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
<TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
</UserControl>
Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT
} --ELSE TO DO?????
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
}
That is one of the many reasons you should never set the DataContext directly from the UserControl itself.
When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded to an instance that only the UserControl has access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.
There are two main ways of using UserControls in WPF
A standalone UserControl that can be used anywhere without a specific DataContext being required.
This type of UserControl normally exposes DependencyProperties for any values it needs, and would be used like this:
<v:InkStringView TextInControl="{Binding SomeValue}" />
Typical examples I can think of would be anything generic such as a Calendar control or Popup control.
A UserControl that is meant to be used with a specific Model or ViewModel only.
These UserControls are far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:
<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
Or more frequently, it would be used with an implicit DataTemplate. An implicit DataTemplate is a DataTemplate with a DataType and no Key, and WPF will automatically use this template anytime it wants to render an object of the specified type.
<Window.Resources>
<DataTemplate DataType="{x:Type m:InkStringViewModel}">
<v:InkStringView />
</DataTemplate>
<Window.Resources>
<!-- Binding to a single ViewModel -->
<ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
<!-- Binding to a collection of ViewModels -->
<ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
No ContentPresenter.ItemTemplate or ItemsControl.ItemTemplate is needed when using this method.
Don't mix these two methods up, it doesn't go well :)
But anyways, to explain your specific problem in a bit more detail
When you create your UserControl like this
<v:InkStringView TextInControl="{Binding text}" />
you are basically saying
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContext is not specified anywhere in the XAML, so it gets inherited from the parent item, which results in
vw.DataContext = Strings[x];
so your binding that sets TextInControl = vw.DataContext.text is valid and resolves just fine at runtime.
However when you run this in your UserControl constructor
this.DataContext = new InkStringViewModel();
the DataContext is set to a value, so no longer gets automatically inherited from the parent.
So now the code that gets run looks like this:
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
and naturally, InkStringViewModel does not have a property called text, so the binding fails at runtime.
You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.
UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.
Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.
If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."
Seems like you are mixing the model of the parent view with the model of the UC.
Here is a sample that matches your code:
The MainViewModel:
using System.Collections.Generic;
namespace UCItemsControl
{
public class MyString
{
public string text { get; set; }
}
public class MainViewModel
{
public ObservableCollection<MyString> Strings { get; set; }
public MainViewModel()
{
Strings = new ObservableCollection<MyString>
{
new MyString{ text = "First" },
new MyString{ text = "Second" },
new MyString{ text = "Third" }
};
}
}
}
The MainWindow that uses it:
<Window x:Class="UCItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:UCItemsControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<v:MainViewModel></v:MainViewModel>
</Window.DataContext>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Your UC (no set of DataContext):
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
}
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
(Your XAML is OK)
With that I can obtain what I guess is the expected result, a list of values:
First
I am row 1
Second
I am row 1
Third
I am row 1
You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).
1) Remove this.DataContext = new InkStringViewModel(); from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.
2) Change
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
to
<v:InkStringView TextInControl="{Binding }" />
The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.
<v:InkStringView TextInControl="{Binding SomeProperty}" />

How to create a UserControl which accepts adding child elements in XAML

Goal:
I want to achieve something similar to the GroupBox control.
I want to have some design and controls wrapping around a child element which I specify in XAML:
Current code:
<GroupBox Header="Filter">
<local:Filterview></local:Filterview>
</GroupBox>
Goal:
<objectViews:ContractableGroupBox Header="TEST">
<local:Filterview></local:Filterview>
</objectViews:ContractableGroupBox>
Current Situation / Issue:
My custom "groupbox" works as far as adding it to the Form and setting the header but it does not work properly when adding the child element Filterview.
Working (But no content):
<objectViews:ContractableGroupBox Header="TEST">
</objectViews:ContractableGroupBox>
Bugs out (content is there but wrapping not):
<objectViews:ContractableGroupBox Header="TEST">
<local:Filterview></local:Filterview>
</objectViews:ContractableGroupBox>
Code Behind:
This is the XAML of ContractableGroupBox:
<UserControl x:Class="SoundStudio.Views.ObjectViews.ContractableGroupBox"
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:local="clr-namespace:SoundStudio.Views.ObjectViews"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Border BorderBrush="#FF303030" Background="#FF646464" CornerRadius="8,8,3,3" >
<Expander x:Name="ExpanderContent" Header="{Binding Header}" IsExpanded="True">
</Expander>
</Border>
</Grid>
</UserControl>
Note, I want to specify the child element in the parent UserControl, but it should be displayed as if in the Expander node such as:
<Expander x:Name="ExpanderContent" Header="{Binding Header}" IsExpanded="True">
<local:Filterview></local:Filterview>
</Expander>
This is the current ContractableGroupBox.cs
using System.Windows.Controls;
namespace SoundStudio.Views.ObjectViews
{
/// <summary>
/// Interaction logic for ContractableGroupBox.xaml
/// </summary>
public partial class ContractableGroupBox : UserControl
{
public ContractableGroupBox()
{
InitializeComponent();
this.DataContext = this;
}
public string Header { get; set;}
}
}
What you see ist that the following XAML overrides the content of your UserControl, which includes the Grid and the Expander itself and that is why the header is seemingly lost.
<objectViews:ContractableGroupBox Header="TEST">
<local:Filterview></local:Filterview>
</objectViews:ContractableGroupBox>
As a general advise, do not ever set the DataContext of a UserControl to itself, this will break data context inheritance and is bad practice. Regarding your issue, you should make Header a dependency property to enable data binding and add another dependency property for the content of the expander, e.g. ExpanderContent (Content already exists on UserControl).
[ContentProperty(nameof(ExpanderContent))]
public partial class ContractableGroupBox : UserControl
{
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
nameof(Header), typeof(string), typeof(ContractableGroupBox));
public static readonly DependencyProperty ExpanderContentProperty = DependencyProperty.Register(
nameof(ExpanderContent), typeof(object), typeof(ContractableGroupBox));
public ContractableGroupBox()
{
InitializeComponent();
}
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public object ExpanderContent
{
get => GetValue(ExpanderContentProperty);
set => SetValue(ExpanderContentProperty, value);
}
}
The ContentProperty attribute at the top will make sure that anything you put inside this user control in XAML like below will be assigned to the ExpanderContent property instead of the Content property that the UserControl type already provides. If you do not do this, you have to assign your content manually to ExpanderContent, otherwise the actual content of the UserControl itself (your Grid, Expander, etc. will be overridden.
<objectViews:ContractableGroupBox Header="TEST">
<local:Filterview></local:Filterview>
</objectViews:ContractableGroupBox>
You have to change your user control XAML bindings using RelativeSource and AncestorType, so that they resolve the dependency properties Header and ExpanderContent on your control. Notice, that I renamed the Expander to Expander to avoid a naming collision with the dependency property ExpanderContent. Now that the bindings use the dependency properties, there is even no need for setting the DataContext.
<UserControl x:Class="SoundStudio.Views.ObjectViews.ContractableGroupBox"
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:local="clr-namespace:SoundStudio.Views.ObjectViews"
mc:Ignorable="d" >
<Grid>
<Border BorderBrush="#FF303030" Background="#FF646464" CornerRadius="8,8,3,3" >
<Expander x:Name="Expander"
IsExpanded="True"
Header="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Content="{Binding ExpanderContent, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
</Border>
</Grid>
</UserControl>
However, if the only thing that you want to add to the Expander is a Border around it, then you do not have to create a separate UserControl. You could just create a custom control template for Expander by copying its default template and add a Border there.

How to set a parameter for a user control that has a view model in WPF

In WPF I have a window that includes a user control. The window and user control each have a view model. I want to pass a parameter from the window's VM to the UC's VM. After a fair amount of looking, I haven't found a way.
The window XAML sets its data context to its VM. The UC includes a custom dependency property for the parameter. I want to use SetBinding to bind the DP to the UC VM.
If I set the UC data context to its VM, then the parameter binding doesn't work. If I don't set the UC data context then the parameter binding works but the UC VM is not referenced.
How can I pass a parameter AND bind to the UC VM?
UC XAML
<UserControl x:Name="userControl" x:Class="Test_Paramaterized_UserControl_with_MVVM.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"
xmlns:local="clr-namespace:Test_Paramaterized_UserControl_with_MVVM"
xmlns:view="clr-namespace:Daavlin.SmartTouch.STUV_WPF.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Margin="10">
<Border BorderThickness="3" BorderBrush="Black" Padding="10">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserControl1 View: "/>
<TextBlock Text="{Binding ElementName=userControl, Path=PropUserControlView, Mode=OneWay}" FontWeight="Bold"/>
</StackPanel>
<Rectangle Height="5"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserControl1 ViewModel: " />
<TextBlock Text="{Binding PropUserControlViewModel, FallbackValue=propUserControlViewModel 2}" FontWeight="Bold">
<TextBlock.DataContext>
<local:UserControl1ViewModel/>
</TextBlock.DataContext>
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</Grid>
</UserControl>
UC code-behind & VM
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string PropUserControlView { get => (string)GetValue(PropUserControlViewProperty); set => SetValue(PropUserControlViewProperty, value); }
public static readonly DependencyProperty PropUserControlViewProperty =
DependencyProperty.Register(nameof(PropUserControlView), typeof(string), typeof(UserControl1),
new PropertyMetadata(null, DependencyPropertyChanged));
private static void DependencyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var x = dependencyPropertyChangedEventArgs.NewValue;
}
}
public class UserControl1ViewModel : ObservableObject
{
public string PropUserControlViewModel { get => _propUserControlViewModel; set => SetField(ref _propUserControlViewModel, value); }
private string _propUserControlViewModel = "value from UserControl-ViewModel";
}
Window XAML
<Window x:Class="Test_Paramaterized_UserControl_with_MVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test_Paramaterized_UserControl_with_MVVM"
Title="MainWindow" >
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid VerticalAlignment="Top" >
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Margin="20">
<StackPanel Orientation="Horizontal">
<TextBlock Text="MainWindow1 ViewModel: "/>
<TextBox Text="{Binding PropWindowViewModel, UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold"/>
</StackPanel>
<Rectangle Height="10"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserControl1 (fixed value Fixed): " VerticalAlignment="Center"/>
<local:UserControl1 PropUserControlView="Fixed"/>
</StackPanel>
<Rectangle Height="10"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserControl1 (bound to MainWindows VM): " VerticalAlignment="Center"/>
<local:UserControl1 PropUserControlView="{Binding PropWindowViewModel}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
Window code-behind & VM
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MainWindowViewModel : ObservableObject
{
public string PropWindowViewModel { get => _propWindowViewModel; set => SetField(ref _propWindowViewModel, value); }
private string _propWindowViewModel = "valuefrom Window-VIewModel";
}
As far as I understood, what you meant was :-
1) You have a user control which has its own view model.
2) You have a Window where you have its own view model.
You want to link both and pass parameters from your WindowViewModel to UserControlViewModel.
What you can do is, Keep a property (e.g. UCViewModel) of type UserControlViewModel in your WindowViewModel and set the datacontext of the user control in your XAML to
<local:UserControl1 DataContext="{Binding UCViewModel}" .../>
Now that you can access anything that is there in your UserControlViewModel via WindowViewModel, you can set any property value OR pass any parameter to your UserControlViewModel from WindowViewModel.
If you need a code reference, let me know. We have been using user controls in a similar way and it works fine.
I want to use SetBinding to bind the DP to the UC VM.
Is that really a requirement? SetBinding() requires that the target property be a dependency property, which in turn requires that the target object be a dependency object. Your view model object is not a dependency object, and of course none of its properties are dependency properties.
Achieving that goal would require a much bigger change to your code than is otherwise apparently necessary.
If I set the UC data context to its VM, then the parameter binding doesn't work
Why not? You didn't show code that attempts this, so it's difficult to understand what you mean here. It's not a good idea to have the user control set its own DataContext anyway. That property is public, and you don't want to expose your implementation details to client code. Doing so invites bugs where the client code has set the DataContext to the wrong thing, disabling everything in your UserControl.
But that said, if by "parameter binding" you mean the binding in the MainWindow XAML, assigning {Binding PropWindowViewModel} to the PropUserControlView property of the user control, then just setting the DataContext of the user control should not affect that. You still have the dependency property in the user control, and anything bound that within the user control should still work.
Finally, it's not entirely clear why you want the dependency property tied to the view model. In the user control's XAML, you can (as you've already done) bind directly to the user control's dependency property. There's no need for a property in the view model to replicate that.
Maybe you have code in the view model somewhere else that wants to respond to changes in this value? It's not clear, and it's difficult to give the best advice without knowing the whole story.
All that said, the code you posted above can be made to work with a couple of small changes. First, you'll need to expose the TextBlock where you've created the view model, so that the user control code-behind has access to it:
<TextBlock x:Name="textBlock1" Text="{Binding PropUserControlViewModel, FallbackValue=propUserControlViewModel 2}" FontWeight="Bold">
<TextBlock.DataContext>
<l:UserControl1ViewModel/>
</TextBlock.DataContext>
</TextBlock>
I.e. add the x:Name="textBlock1" to the declaration.
Then, you need to use the property-change notification for your dependency property to update the view model property any time the dependency property changes:
private static void DependencyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
UserControl1 uc = (UserControl1)dependencyObject;
UserControl1ViewModel vm = (UserControl1ViewModel)uc.textBlock1.DataContext;
vm.PropUserControlViewModel = (string)dependencyPropertyChangedEventArgs.NewValue;
}
The above works in your limited example, but you'll probably want to give the DependencyPropertyChanged() method a more descriptive name, specific to the actual property in question.
If you do choose to mirror the dependency property in the view model this way, IMHO a better way to do that would be to set the user control's root element (i.e. the Grid) so that its data context is your view model, and then throughout the rest of the XAML, bind only to the view model. Mixing the view model and dependency property is not wrong per se, but it does introduce an inconsistency that can make it harder to test and maintain the code.

How to bind WPF TextBox to a propperty that is the member of application main window

I have a private field
private static Double myValue;
in the application MainWindow class. And there (in the MainWindow class) I defined a property
public static Double MytValue
{
get { return myValue; }
}
In the structure of the MainWindow class I have a TextBox. I'm in need of binding it to the MytValue property. In XAML I write:
<TextBox Name="tbxMyValue" Grid.Row="1" Grid.Column="2" TextAlignment="Center"
Text="{Binding Path=MyValue}" Width="Auto" Margin="10,0,10,15" IsEnabled="True" />
But it has no effect. I see nothing in the TextBox while myValue variable has a value. Why? Please help me.
I like to set the DataContext in the Window section
<Window x:Class="Gabe3a.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Gabe3a"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:System="clr-namespace:System;assembly=mscorlib"
DataContext="{Binding RelativeSource={RelativeSource self}}"
Title="Gabriel Main Ver 3a01" Icon="faviconw.ico" Height="600" Width="800">
You need to set the DataContext behind the Window for that binding to work
There are two layers to an application with WPF: the UI layer and the data layer.
The Data layer for an application starts out as null, and you can set it using the DataContext property.
Whenever you do a basic binding in WPF, you are binding to the DataContext. So Text="{Binding Path=MyValue}" is actually saying "Get the MyValue property from the current DataContext".
You could simply set the DataContext in your code behind:
MyWindow.DataContext = this;
Or you can use a RelativeSource binding to tell WPF to get the MyValue property from somewhere else, such as telling it to get it from the closest Window it finds when navigating up the VisualTree:
Text="{Binding Path=MyValue, RelativeSource={
RelativeSource AncestorType={x:Type Window}}}"
I actually have an article on my blog about the DataContext that I'd recommend reading if you're new to WPF and the DataContext: What is this "DataContext" you speak of?
It's not how it works. Binding to static properties; {Binding Source={x:Static local:Application.MyValue}}
Note that your field needs to be property & public. If you want to go with your solution, you need to set DataContext as {RelativeSource Self}.
In file MainWindow.xaml.cs in constructor add this line:
DataContext = this;
after InitializeComponent();

How to set a PlacementTarget for a WPF tooltip without messing up the DataContext?

I have a typical MVVM setup of Listbox and vm + DataTemplate and item vm's. The data templates have tooltips, which have elements bound to the item vm's. All works great.
Now, I'd like to have the tooltip placed relative to the listbox itself. It's fairly large and gets in the way when casually mousing over the listbox. So I figured I'd do something like this in the DataTemplate:
<Grid ...>
<TextBlock x:Name="ObjectText"
ToolTipService.Placement="Left"
ToolTip="{StaticResource ItemToolTip}"
ToolTipService.PlacementTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}">
</TextBlock>
...
...with the static resource...
<ToolTip x:Key="ItemToolTip">
<StackPanel>
<TextBlock Text="{Binding DisplayName.Name}"/>
<TextBlock Text="{Binding Details}" FontStyle="Italic"/>
...
</StackPanel>
</ToolTip>
Here's my problem. When I use that PlacementTarget I get a binding error that the DisplayName.Name and Details are not binding. The object it's trying to bind to is not the item vm but the overall Listbox vm.
So my question is: how can I set the ToolTipService.PlacementTarget for a tooltip yet keep the DataContext inherited from its owner?
Ok, a friend at work mostly figured it out for me. This way is super clean, doesn't feel hacky.
Here's the basic problem: as user164184 mentioned, tooltips are popups and therefore not part of the visual tree. So there's some magic that WPF does. The DataContext for the popup comes from the PlacementTarget, which is how the bindings work most of the time, despite the popup not being part of the tree. But when you change the PlacementTarget this overrides the default, and now the DataContext is coming from the new PlacementTarget, whatever it may be.
Totally not intuitive. It would be nice if MSDN had, instead of spending hours building all those pretty graphs of where the different tooltips appear, said one sentence about what happens with the DataContext.
Anyway, on to the SOLUTION! As with all fun WPF tricks, attached properties come to the rescue. We're going to add two attached properties so we can directly set the DataContext of the tooltip when it's generated.
public static class BindableToolTip
{
public static readonly DependencyProperty ToolTipProperty = DependencyProperty.RegisterAttached(
"ToolTip", typeof(FrameworkElement), typeof(BindableToolTip), new PropertyMetadata(null, OnToolTipChanged));
public static void SetToolTip(DependencyObject element, FrameworkElement value) { element.SetValue(ToolTipProperty, value); }
public static FrameworkElement GetToolTip(DependencyObject element) { return (FrameworkElement)element.GetValue(ToolTipProperty); }
static void OnToolTipChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
ToolTipService.SetToolTip(element, e.NewValue);
if (e.NewValue != null)
{
((ToolTip)e.NewValue).DataContext = GetDataContext(element);
}
}
public static readonly DependencyProperty DataContextProperty = DependencyProperty.RegisterAttached(
"DataContext", typeof(object), typeof(BindableToolTip), new PropertyMetadata(null, OnDataContextChanged));
public static void SetDataContext(DependencyObject element, object value) { element.SetValue(DataContextProperty, value); }
public static object GetDataContext(DependencyObject element) { return element.GetValue(DataContextProperty); }
static void OnDataContextChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
var toolTip = GetToolTip(element);
if (toolTip != null)
{
toolTip.DataContext = e.NewValue;
}
}
}
And then in the XAML:
<Grid ...>
<TextBlock x:Name="ObjectText"
ToolTipService.Placement="Left"
ToolTipService.PlacementTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
mystuff:BindableToolTip.DataContext="{Binding}">
<mystuff:BindableToolTip.ToolTip>
<ToolTip>
<StackPanel>
<TextBlock Text="{Binding DisplayName.Name}"/>
<TextBlock Text="{Binding Details}" FontStyle="Italic"/>
...
</StackPanel>
</ToolTip>
</mystuff:BindableToolTip.ToolTip>
</TextBlock>
...
Just switch the ToolTip over to BindableToolTip.ToolTip instead, then add a new BindableToolTip.DataContext that points at whatever you want. I'm just setting it to the current DataContext, so it ends up inheriting the viewmodel bound to the DataTemplate.
Note that I embedded the ToolTip instead of using a StaticResource. That was a bug in my original question. Obviously has to be generated unique per item. Another option would be to use a ControlTemplate Style trigger thingy.
One improvement could be to have BindableToolTip.DataContext register for notifications on the ToolTip changing, then I could get rid of BindableToolTip.ToolTip. A task for another day!
ToolTips are not part of the visual tree as they are popup based. So your placement target biding (which uses Visual Tree search) to get the relative ancestor wont work. Why not use ContentHacking instead? This way one hacks into the visual tree from the logical elements such as ContextMenu, Popups, ToolTip etc...
Declare a StaticResource which is any FrameworkElement (we need support for data context).
<UserControl.Resources ...>
<TextBlock x:Key="ProxyElement" DataContext="{Binding}" />
</UserControl.Resources>
Supply a content control in the Visual Tree and set this static resource "ProxyElement" as its content.
<UserControl ...>
<Grid ...>
<ItemsControl x:Name="MyItemsControl"
ItemsTemplate="{StaticResource blahblah}" .../>
<ContentControl Content="{StaticResource ProxyElement}"
DataContext="{Binding ElementName=MyItemsControl}" Visibility="Collapsed"/>
What the above steps have done that "ProxyElement" has been connected to the ItemsControl (which serves as a DataContext) and it is available as a SaticResource to be used anywhere.
Now use this StaticResource as a source for any bindings which are failing in your tooltip...
<Grid ...>
<TextBlock x:Name="ObjectText"
ToolTipService.Placement="Left"
ToolTip="{StaticResource ItemToolTip}"
PlacementTarget="{Binding Source={StaticResource ProxyElement}, Path=DataContext}" ... /> <!-- This sets the target as the items control -->
and
<ToolTip x:Key="ItemToolTip">
<StackPanel DataContext="{Binding Source={StaticResource ProxyElement}, Path=DataContext.DataContext}"><!-- sets data context of the items control -->
<TextBlock Text="{Binding DisplayName.Name}"/>
<TextBlock Text="{Binding Details}" FontStyle="Italic"/> ...
</StackPanel>
</ToolTip>
Let me know if this helps...
As I understand [but I probably wrong (no harm in trying)], you can initialize your items with reference to objects which were used in ancestor DataContext, i.e.
public class ItemsVM<T> : VMBase
{
public T parentElement;
public ItemsVM (T _parentElement)
{
this.parentElement = _parentElement;
}
...
}

Categories

Resources