I have a quite common design MVVM app: MainWindow has a ContentPresenter defined like this:
<ContentPresenter Grid.Row="1" Grid.Column="1"
Content="{Binding Path=CurrentViewModel}">
</ContentPresenter>
It uses DataTemplate and can switch Views:
<DataTemplate DataType="{x:Type vm:PlateEntireViewModel}">
<v:PlateEntireView/>
</DataTemplate>
PlateEntireView is a UserControl with PlateEntireViewModel as DataContext. Now - I want to have a property in PlateEntireViewModel, that would hold PlateEntireView actual position (Left,Top) inside the MainWindow. Can this be acieved? Is it possible to make some DependencyProperty and use it in PlateEntireView, like:
<Grid ext:CustomProperties.ActualPositionX="{Binding Path=ActualPositionX, Mode=OneWayToSource}">
</Grid>
Can anybody tell me if its the right way to try - and how to use it?
So the shortest answer for this is normally the ViewModel would not care about specific coordinates that are being displayed in the View. That being said it is possible to do this relatively simple.
All you need to do is set up an attached property that will retrieve the point from the left top corner of the screen
public static double GetXCoordinate(DependencyObject obj)
{
var fe = obj as FrameworkElement;
if (fe != null)
{
return (fe.PointToScreen(new Point())).X;
}
return -1;
}
public static void SetXCoordinate(DependencyObject obj, double value)
{
obj.SetValue(XCoordinateProperty, value);
}
// Using a DependencyProperty as the backing store for XCoordinate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty XCoordinateProperty =
DependencyProperty.RegisterAttached("XCoordinate", typeof(double), typeof(CustomProperties), new PropertyMetadata(0.0));
Then you could have the you binding look as such
<local:control Grid.Column="1"
Grid.Row="1"
x:Name="cp"
local:CustomProperties.XCoordinate="{Binding XCoordinate, UpdateSourceTrigger=Explicit}"
/>
You will need to update explicitly though, since this binding will never fire off any change events. You can do that by hooking into a reasonable event in your view. For more information about that look here.
Related
tl;dr: I want the consumer of my custom control to be able to define which separate resources two inner ContentPresenters of the custom control are to use. Side note: the ContentPresenters themselves will be part of DataTemplates, i.e. not explicit members of the custom control.
I'm creating a custom control that will use two ContentPresenter instances. The ContentTemplate used by each of these instances should be determined by defining DataTemplates using <DataTemplate DataType="{x:Type someType}">, i.e. the presenters will find the right template based on the data type.
As I understand, a ContentPresenter will search through static resources, starting with its' own, then traversing up the logical tree and, finally, checking for application-level resources.
Developers using the custom control should be able to specify a ResourceDictionary for each of the two ContentPresenter instances.
Just to illustrate my intention: if I were wanting to customize two DataTemplates for each of the presenters, I could so something like this:
CustomControl.xaml.cs snippet:
public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register(
nameof(HeaderTemplate), typeof(DataTemplate), typeof(DynamicDataGridControl), new PropertyMetadata(default(DataTemplate)));
public DataTemplate HeaderTemplate
{
get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
}
public static readonly DependencyProperty CellTemplateProperty = DependencyProperty.Register(
nameof(CellTemplate), typeof(DataTemplate), typeof(DynamicDataGridControl), new PropertyMetadata(default(DataTemplate)));
public DataTemplate CellTemplate
{
get { return (DataTemplate)GetValue(CellTemplateProperty); }
set { SetValue(CellTemplateProperty, value); }
}
CustomControl.xaml snippet
<ContentPresenter x:Name="HeaderPresenter" ContentTemplate="{Binding ElementName=self, Path=HeaderTemplate}"/>
...
<ContentPresenter x:Name="CellPresenter" ContentTemplate="{Binding ElementName=self, Path=CellTemplate}"/>
And the others could use the custom control like this:
ConsumingControl.xaml snippet
<UserControl.Resources>
<DataTemplate x:Key="MyHeaderTemplate">
...
</DataTemplate>
<DataTemplate x:Key="MyCellTemplate">
...
</DataTemplate>
</UserControl.Resources>
<CustomControl HeaderTemplate="{StaticResource MyHeaderTemplate}" CellTemplate="{StaticResource MyCellTemplate}"/>
Now I would like to do basically the same, but instead of explicitly defining the ContentTemplate, I would like to define the ResourceDictionary or scope for each control.
I tried adding a DependencyProperty of type ResourceDictionary to my custom control and then referencing it as shown below, but it only leads to an exception: A 'Binding' cannot be set on the 'Source' property of type 'ResourceDictionary'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
<ContentPresenter>
<ContentPresenter.Resources>
<ResourceDictionary Source="{Binding ElementName=self, Path=CellResources}"></ResourceDictionary>
</ContentPresenter.Resources>
</ContentPresenter>
The reason for this is, that the types may be the same for both content presenters, but they should be rendered differently. I could achieve what I need (more or less) by specifying a converter, but I would much prefer to do it as outlined above. Is this possible?
Thanks in advance once again!
Can we implement dependency property in a class that does not inherit from DependencyObject? If yes what is the difference?
Yes you can. This is a variant of an Attached Property. From How to Create an Attached Property:
If your class is defining the attached property strictly for use on other types, then the class does not have to derive from DependencyObject. But you do need to derive from DependencyObject if you follow the overall WPF model of having your attached property also be a dependency property.
The difference begins in how you define an attached property. Instead of Register, you have to use the RegisterAttached method and you have to define the get and set accessors as static methods with the following naming convention, where PropertyName is the name of the attached property.
public static object GetPropertyName(object target)
public static void SetPropertyName(object target, object value)
Let's look at a simple example. Suppose you want create a button that shows an image and a text side-by side. Unfortunately, a Button only has a single Content. As you do not want to create a custom control right now, you try to solve the issue by creating a content template and an attached property for the path of an image to display.
public static class IconButtonProperties
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source", typeof(string), typeof(IconButtonProperties));
public static void SetSource(UIElement element, string value)
{
element.SetValue(SourceProperty, value);
}
public static string GetSource(UIElement element)
{
return (string) element.GetValue(SourceProperty);
}
}
Now you can attach this property to a button in your view to define an image path. Here the attached property differs in that you define it on a different type (Button) using its owner type IconButtonProperties.
<Button ContentTemplate="{StaticResource ImageTextContentTemplate}"
local:IconButtonProperties.Source="Resources/MyImage.png"
Content="Click me!"/>
The last big difference is shown in the data template that uses the attached property with a Binding. When binding to an attached property, you have to put the property in parentheses.
<DataTemplate x:Key="ImageTextContentTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding (local:IconButtonProperties.Source), RelativeSource={RelativeSource AncestorType={x:Type Button}}}"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Margin="5, 0, 0, 0"
Text="{Binding}"/>
</Grid>
</DataTemplate>
As you can see, attached properties are invaluable for extensibility and binding in WPF. For more information on attached properties in general, you can refer to the documentation:
Attached Properties Overview
How to: Register an Attached Property
Binding Path Syntax
DependencyProperty.RegisterAttached Method
DependencyProperty.RegisterAttachedReadOnly Method
Is it not possible to have multiple layers of UserControls containing ContentControl?
I am trying to create Views for different Models that are derived, so I'd like to eliminate the need to re-create the Views for each object type, and instead provide a ContentControl, or a ContentPresenter to inject the "rest of the view". However, I can only go one level deep with this method.
Here's a simplified example.(I've removed some of the xmlns). In my case, I'm working with significantly more complex Views, and trying to eliminate duplicate code in multiple places, concerned for changes later.
I have a Base UserControl, we'll call it UserControlLevel1
<UserControl x:Class="ContentControlNesting.UserControlLevel1"
x:Name="userControlLevel1"
xmlns:local="clr-namespace:ContentControlNesting">
<StackPanel>
<TextBlock Text="UserControlLevel1ContentTop"/>
<ContentControl Content="{Binding ElementName=userControlLevel1, Path=ChildContent}"/>
<TextBlock Text="UserControlLevel2ContentBottom"/>
</StackPanel>
</UserControl>
It has the following DependencyProperty on the UserControl
namespace ContentControlNesting
{
public partial class UserControlLevel1 : UserControl
{
public UserControlLevel1()
{
InitializeComponent();
}
public static readonly DependencyProperty ChildContentProperty = DependencyProperty.Register("ChildContent", typeof(UIElement), typeof(UserControlLevel1), new PropertyMetadata(null));
public UIElement ChildContent
{
get { return (UIElement)GetValue(ChildContentProperty); }
set { SetValue(ChildContentProperty, value); }
}
}
}
The ContentControl will be used in the following UserControl called UserControlLevel2. This UserControl works fine, just the way I would expect. Or rather UserControlLevel1 works properly within UserControlLevel2.
<UserControl x:Class="ContentControlNesting.UserControlLevel2"
x:Name="userControlLevel2"
xmlns:local="clr-namespace:ContentControlNesting">
<local:UserControlLevel1>
<local:UserControlLevel1.ChildContent>
<StackPanel>
<TextBlock Text="UserControlLevel2ContentTop"/>
<ContentControl Content="{Binding ElementName=userControlLevel2, Path=ChildContent}"/>
<TextBlock Text="UserControlLevel2ContentBottom"/>
</StackPanel>
</local:UserControlLevel1.ChildContent>
</local:UserControlLevel1>
</UserControl>
Likewise, it has a single DependencyProperty for the ContentControl on this UserControl like the first. I've also tried this with differently named DependencyProperties.
namespace ContentControlNesting
{
public partial class UserControlLevel1 : UserControl
{
public UserControlLevel1()
{
InitializeComponent();
}
public static readonly DependencyProperty ChildContentProperty = DependencyProperty.Register("ChildContent", typeof(UIElement), typeof(UserControlLevel1), new PropertyMetadata(null));
public UIElement ChildContent
{
get { return (UIElement)GetValue(ChildContentProperty); }
set { SetValue(ChildContentProperty, value); }
}
}
}
Okay, so at this point, everything seems to be working fine. I've added additional content inside of the ContentControl of UserControlLevel1, and I've added another ContentControl within my UserControlLevel2 UserControl.
The problem is when I try to introduce a 3rd Tier of either UserControl or my MainWindow. Anything I add to the ContentControl of UserControlLevel2 just does not appear.
<Window x:Class="ContentControlNesting.MainWindow"
xmlns:local="clr-namespace:ContentControlNesting"
Title="MainWindow" Height="200" Width="300">
<local:UserControlLevel2>
<local:UserControlLevel2.ChildContent>
<StackPanel>
<TextBlock Text="Main Window Content Text"/>
</StackPanel>
</local:UserControlLevel2.ChildContent>
</local:UserControlLevel2>
</Window>
Am I trying to do something that's not possible? Or am I doing something wrong with ContentControl and the DependencyProperties? Should I be looking at this with a different approach?
It is possible. The system cannot resolve the ElementName in the Binding. The solution is to use the relative binding. Just replace the following line in UserControlLevel2 and your are done:
<ContentControl Content="{Binding Path=ChildContent, RelativeSource={RelativeSource AncestorType={x:Type local:UserControlLevel2}}}"/>
I'm having some issues with binding some custom controls in a Windows Phone app right now. Usually this is never an issue but apparently my mind can't comprehend this today.
So I'm doing an MVVM style setup which is good. I have my page with a view and also a viewmodel. Now on a WebClient callback I assign the dataContext of my view to the list of models in my ViewModel, nice and simple thus far...now in my view I created a ListBox with a custom control in the datatemplate which is basically a cell in the list. I once again set my user controls dataContext to binding, and binding all the models values to the regular UI elements works no problem.
Here's a sample:
<Grid Grid.Column="0">
<Image Source="{Binding SmallPath}" VerticalAlignment="Top"/>
</Grid>
<Grid Grid.Column="1">
<StackPanel Margin="12,0,0,0">
<TextBlock x:Name="MemberId_TextBlock" Text="{Binding MemberId}" FontSize="28"
Margin="0,-8,0,0"
Foreground="{StaticResource PhoneForegroundBrush}"/>
<StackPanel Orientation="Horizontal" Margin="0,-11,0,0">
<TextBlock Text="{Binding DaysReported}" FontSize="42"
Margin="0,0,0,0"
Foreground="{StaticResource PhoneAccentBrush}"/>
<TextBlock Text="days" FontSize="24"
Margin="3,19,0,0"
Foreground="{StaticResource PhoneSubtleBrush}"/>
</StackPanel>
</StackPanel>
</Grid>
That's in my user control, and here's the the view where the usercontrol is housed:
<Grid x:Name="LayoutRoot" Background="Transparent">
<ListBox Name="TopSpotter_ListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<!--<TextBlock Text="{Binding MemberId}"/>-->
<controls:TopSpotterItemControl DataContext="{Binding}"/>
<Grid Height="18"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Now this is good enough but what I want to do in my view is set data from my model like Booleans that determine whether or not I should show certain Grids etc. So if I try to set a dependency property explicitly in my control it fires and will run logic in the Getter/Setters for instance. HOWEVER if I try to set these custom objects from a binding source it won't actually set.
Here's what works:
<controls:TopSpotterItemControl ChampVisibility="True">
This way will trigger the ChampVisibility property and then in the code behind of the user control I can set visibilities.
Here's what fails but I want to work:
<controls:TopSpotterItemControl ChampVisibility="{Binding IsChamp">
In addition I can still set the DataContext to {Binding} and the result will be unchanged.
In this scenario IsChamp is part of my model that I would like to bind to this user control which I guess comes from the dataContext being set on the view from the viewModel. I'm not sure what I can do to get this so the bindings work etc. without having to set custom properties.
Finally, here's my user control:
public partial class TopSpotterItemControl : UserControl
{
public string MemberId
{
get
{
return this.MemberId_TextBlock.Text;
}
set
{
this.MemberId_TextBlock.Text = value;
}
}
public bool ChampVisibility {
set
{
if (value)
{
this.Champ_Grid.Visibility = System.Windows.Visibility.Visible;
}
}
}
public static readonly DependencyProperty MemberNameProperty =
DependencyProperty.Register("MemberId", typeof(string), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(null));
public TopSpotterItemControl()
{
InitializeComponent();
}
}
Bit long winded and I hope I made things on the issue clear. My one major hang up so far, and I'd like to abstract as much control as I can to the user control via dependency properties explicitly set in xaml, rather than setting up binding in its xaml that depend on the knowledge of a model. Thanks!
Your DependencyProperty is badly formed. (I also don't see Champ_Grid defined in your class or XAML, but I assume that is an ommission)
Setting ChampVisibility = true in code works because it is unrelated to the DependencyProperty.
You can tell easily because the default value for your DP is invalid. It will compile, but the instance constructor will through an exception if it is ever invoked.
new PropertyMetadata(null)
bool = null = exception
If you call GetValue(TopSpotterItemControl.ChampVisibilityProperty) from somewhere you can confirm all of the above.
You should make changes to instance fields in the property changed handler and declare the property like the following, it will work:
Note that the property has to change (not just be set) for the event to be raised.
public bool ChampVisibility
{
get { return (bool)GetValue(ChampVisibilityProperty); }
set { SetValue(ChampVisibilityProperty, value); }
}
public static readonly DependencyProperty ChampVisibilityProperty =
DependencyProperty.Register("ChampVisibility ", typeof(bool), typeof(TopSpotterItemControl), new PropertyMetadata(true, (s, e) =>
{
TopSpotterItemControl instance = s as TopSpotterItemControl;
instance.Champ_Grid.Visibility = instance.ChampVisibility ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
}));
Incidentally, your MemberId DependencyProperty is also completely wrong and cannot work.
Note:
The Binding on your TextBox works, because it is binding to the DataContext (your model), so it probably shows the right value.
The Dependency property in your UserControl will never be set though.
Use the propdp code-snippet in Visual Studio so you dont have to concern yourself with the complexities of Dependency Property declaration.
Also check this out for more info about Dependency Properties
First Control
I can create a UserControl with a DependencyProperty called UserNameLabel. Then, I can just set the datacotext to relativesource self on the UserControl and fill the property in markup.
...
public String UserNameLabel
{
get { return (String)GetValue(UserNameLabelProperty); }
set { SetValue(UserNameLabelProperty, value); }
}
// Using a DependencyProperty as the backing store for UserNameLabel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UserNameLabelProperty =
DependencyProperty.Register("UserNameLabel", typeof(String), typeof(LoginControl), new PropertyMetadata());
<Grid x:Name="LayoutRoot">
<local:LabelTextBox Height="37" Margin="10,24,43,0" VerticalAlignment="Top" Label="{Binding UserNameLabel}"/>
</Grid>
...
Second Control
I can also create a LabelTextBox control and set the relativesource self to it with a similar Label Property.
...
public String Label
{
get { return (String)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
// Using a DependencyProperty as the backing store for Label. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(String), typeof(LabelTextBox), new PropertyMetadata(String.Empty));
...
<Grid x:Name="LayoutRoot">
<TextBlock Height="17" VerticalAlignment="Top" TextWrapping="Wrap" Text="{Binding Label}"/>
However, if I want to nest the LabelTextBox in the first usercontrol I can't seem to put a binding on the Label Property of the LabelTextBox that binds to the UserNameText Property.
It seems like a logical way to create controls where you can set the property of the parent or child control to set the child control's property.
Please help me with this.
No this is not a good approach, you should not assume you have control over any publically available property of a UserControl, that includes the DataContext.
When I want to bind a property of an element to a property of the containing UserControl I use this approach:-
<Grid x:Name="LayoutRoot">
<TextBlock Text="{Binding Parent.Label, ElementName=LayoutRoot}" />
</Grid>
This uses ElementName to set the binding source to the Child of the UserControl. The Parent in the property path then finds the UserControl itself after which we can bind to whatever property that is needed, in this case the Label property.
In this approach you need not muck about with the DataContext.