How do I provide a default value to a binding in a UserControl's XAML, in such a way that Visual Studio will correctly pick up on it?
I'm trying to implement a reusable WPF UserControl, which uses DependencyProperties to customize its behavior, and I'm running into strange behavior in Visual Studio regarding the property's default value.
Take the following control:
Widget.designer.cs
namespace WpfPropertiesTest
{
public partial class Widget : UserControl
{
public Widget()
{
InitializeComponent();
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(nameof(Test), typeof(string), typeof(Widget),
new PropertyMetadata("Hello World"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
}
}
Widget.xaml:
<UserControl x:Class="WpfPropertiesTest.Widget"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_this">
<TextBlock Text="{Binding ElementName=_this, Path=Test, FallbackValue='Fallback', TargetNullValue='Null', Mode=OneWay}" />
</UserControl>
When I look at the designer of Widget.xaml, I see the fallback value, which is reasonable:
However, lets say I now add a Widget to a window:
// TestWindow.xaml
<Window x:Class="WpfPropertiesTest.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfPropertiesTest"
Title="TestWindow" Height="100" Width="200">
<Grid>
<local:Widget />
</Grid>
</Window>
I would expect to see one of either "Hello World", "Fallback", or "Null", but what I actually get is this:
... Which is the name of the property that I'm binding to.
At runtime, using the same code, I can see the default value just fine:
Now, if I explicitly set a value for the property:
<local:Widget Test="A value that I put in." />
The designer now correctly shows:
All of this tells me that my binding is correct, but the Visual Studio (reasonably) doesn't actually look at the dependency properties in the codebehind. This becomes a problem when I'm actually doing something with the property, such as binding visibility to it.
So, what am I missing? Is there some way to give Visual Studio a value to use in the designer? I don't want to have to explicitly set all of my UserControl properties every time I use it, and I would like to have the control behave in the designer as close to runtime as possible.
Should I forego the binding altogether, and just set the value in the codebehind in the property callback?
In a more general sense, is this the correct approach for making reusable UserControls that can be configured by the consumer?
Solution
If I'm not mistaken the reason for that behavior is that you have project code disabled in your designer. To enable project code toggle the rightmost button in the bottom part of the designer, right to the left from the scroll bar.
Diagnosis
Let's do a simple experiment. Firstly, let's create simple test class:
namespace Test
{
public class EmptyClass { }
}
Then let's put this XAML code in some XAML control with a designer:
<Grid xmlns:test="clr-namespace:Test">
<Grid.Resources>
<ObjectDataProvider x:Key="Provider"
ObjectType="test:EmptyClass"
MethodName="GetType" />
</Grid.Resources>
<TextBlock Text="{Binding Source={StaticResource Provider}}" />
</Grid>
Now in the designer view, when project code is enabled, we see Test.EmptyClass. However, when project code is disabled, we see something along the lines of Mocks.Test_EmptyClass_0_96752088. This leads to a conclusion that the designer uses a generated mock class rather than the actual class (which is what I'd expect - that the designer is not using code from my project).
I don't know the exact mechanics of generating mock classes. Perhaps the static constructor for the mocked class is not called, or the mock class does not even derive from the original class. Either way, your code, which assigns the default property metadata, is not (and should not be) executed.
Related
I've been playing around with WPF and MVVM and noticed a strange thing. When using {Binding ElementName=...} on a custom user control, the name of the root element within the user control seems to be visible in the window using the control. Say, here is an example user control:
<UserControl x:Class="TryWPF.EmployeeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TryWPF"
Name="root">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding}"/>
<Button Grid.Column="1" Content="Delete"
Command="{Binding DeleteEmployee, ElementName=root}"
CommandParameter="{Binding}"/>
</Grid>
</UserControl>
Looks pretty legit to me. Now, the dependency property DeleteEmployee is defined in the code-behind, like this:
public partial class EmployeeControl : UserControl
{
public static DependencyProperty DeleteEmployeeProperty
= DependencyProperty.Register("DeleteEmployee",
typeof(ICommand),
typeof(EmployeeControl));
public EmployeeControl()
{
InitializeComponent();
}
public ICommand DeleteEmployee
{
get
{
return (ICommand)GetValue(DeleteEmployeeProperty);
}
set
{
SetValue(DeleteEmployeeProperty, value);
}
}
}
Nothing mysterious here. Then, the window using the control looks like this:
<Window x:Class="TryWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TryWPF"
Name="root"
Title="Try WPF!" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Employees}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<local:EmployeeControl
HorizontalAlignment="Stretch"
DeleteEmployee="{Binding DataContext.DeleteEmployee, ElementName=root}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Again, nothing fancy... except the fact that both the window and the user control have the same name! But I'd expect root to mean the same thing throughout the whole window XAML file, and therefore refer to the window, not to the user control. Alas, the following message is printed when I run it:
System.Windows.Data Error: 40 : BindingExpression path error:
'DeleteEmployee' property not found on 'object' ''String'
(HashCode=-843597893)'.
BindingExpression:Path=DataContext.DeleteEmployee;
DataItem='EmployeeControl' (Name='root'); target element is
'EmployeeControl' (Name='root'); target property is 'DeleteEmployee'
(type 'ICommand')
DataItem='EmployeeControl' (Name='root') makes me think that it treats ElementName=root as referring to the control itself. The fact that it looks for DeleteEmployee on string confirms that suspicion because string is exactly what the data context is in my contrived VM. Here it is, for the sake of completeness:
class ViewModel
{
public ObservableCollection<string> Employees { get; private set; }
public ICommand DeleteEmployee { get; private set; }
public ViewModel()
{
Employees = new ObservableCollection<string>();
Employees.Add("e1");
Employees.Add("e2");
Employees.Add("e3");
DeleteEmployee = new DelegateCommand<string>(OnDeleteEmployee);
}
private void OnDeleteEmployee(string employee)
{
Employees.Remove(employee);
}
}
It is instantiated and assigned to the window in the constructor, which is the only thing in code-behind for the window:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
This phenomenon prompts the following questions:
Is this by design?
If so, how is someone using a custom control supposed to know what name it uses internally?
If Name is not supposed to be used in custom control at all?
If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
Does this have anything to do with the fact that data templates define their own names copes? It doesn't stop me from referring to the main window from within a template if I just rename it so the name doesn't clash with the control.
Your confusion here about how wpf namescopes work is understanable in this situation.
Your issue is simply that you are applying a binding upon a UserControl, which is the "root" (so to speak) of its own namescope. UserControls, and pretty much any container objects, have their own namescopes. These scopes encompass not only child elements, but the object that contains the namescope as well. This is why you can apply x:Name="root" to your window and (except in this one case) locate it from a child control. If you couldn't, namescopes would be pretty much useless.
The confusion comes when you're acting upon a root of a namescope within an encompassing namescope. Your assumption was that the parent's namescope had precedence, but it does not. The Binding is calling FindName on the target object, which in your case is your user control. (Side note, the Binding isn't doing jack, the actual calls can be found in ElementObjectRef.GetObject, but that's where the Binding delegates the call to)
When you call FindName on the root of a namescope, only names defined within this scope are examined. Parent scopes are not searched. (Edit... a bit more reading of the source http://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Data/ObjectRef.cs,5a01adbbb94284c0 starting at line 46 I see that the algorithm walks up the visual tree until it finds a target, so child scopes have precedence over parent scopes)
The result of all this is that you get the user control instance instead of the window, like you were hoping. Now, to answer your individual questions...
1. Is this by design?
Yep. Otherwise namescopes wouldn't work.
2. If so, how is someone using a custom control supposed to know what name it uses internally?
Ideally, you wouldn't. Just like you don't ever want to have to know the name of the root of a TextBox. Interestingly, though, knowing the names of templates defined within a control is often important when attempting to modify it's look and feel...
3. If Name is not supposed to be used in custom control at all?
If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
No! It's fine. Use it. If you aren't sharing your UserControl with other people, just make sure to change its name if you are experiencing this particular problem. If you aren't having any problem, reuse the same name all day, it isn't hurting anything.
If you ARE sharing your UserControl, you should probably rename it to something that won't conflict with other people's names. Call it MuhUserControlTypeName_MuhRoot_Durr or something.
4. If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
Nah. Just change the x:Name of your user control and move on.
5. Does this have anything to do with the fact that data templates define their own names copes? It doesn't stop me from referring to the main window from within a template if I just rename it so the name doesn't clash with the control.
No, I don't believe so. I don't think there is any good reason for it to be, anyhow.
I have some issues with my DependencyProperty in a custom UserControl.
I need to display informations about people in a particular way. To achieve this, I have several UserControls that receive a List<PeopleList> which contains (obviously) one or more People.
Let me show you my (simplified) code and I'll then explain to you the actual behavior of my app.
Here is my UserControl :
public abstract class PeopleLine : UserControl
{
public static readonly DependencyProperty PeopleListProperty =
DependencyProperty.Register("PeopleList", typeof(List<PeopleModel>), typeof(PeopleLine), new PropertyMetadata(default(List<PeopleModel>)));
public List<PeopleModel> PeopleList
{
get { return (List<PeopleModel>)GetValue(PeopleListProperty); }
set { SetValue(PeopleListProperty, value); }
}
}
Then my xaml :
<local:PeopleLine
x:Class="MyApp.Controls.EventSheet.OnePeople"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Controls.EventSheet"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
Margin="0 5"
VerticalAlignment="Top"
Height="51">
<TextBlock
Grid.Column="1"
HorizontalAlignment="Center"
Foreground="Red"
FontSize="25"
Text="{Binding PeopleList[0].Name}"/>
</Grid>
</local:PeopleLine>
And this all starts with my Page which contains an ItemsControl with a correct ItemsSource (I already checked it) and an ItemTemplateSelector (also working perfectly). Here is one of the DataTemplate used by the selector :
<DataTemplate x:Key="OnePeople">
<peoplecontrols:OnePeople
PeopleList="{Binding LinePeopleList}"/>
</DataTemplate>
I'm using several Models That are not really important here since I simplified my code to only have the most important information.
So, back to my issue. When replacing the peoplecontrols:OnePeople in the selector's DataTemplate by a string and putting LinePeopleList[0].Nameas Text, I have the correct text displayed, proving me that my data is correct at this point.
Problem is that when putting back my peoplecontrols:OnePeople, my DependencyProperty is never set. I put a breakpoint at PeopleList's setter and it never triggers.
I tried several modifications (especially those that are given in this post, so replacing the typeof(List<PeopleModel>)by typeof(object) has already been tried) with no success. Also, I tried to replace my DependencyProperty to a string and directly send the name in the DataTemplate but the setter is still not called...
I have no more ideas now and don't understand what's wrong with my code. Any help would be greatly appreciated.
Thanks in advance.
Thomas
Try adding the following line in your UserControl's Constructor, after the call to InitializeComponent:
(this.Content as FrameworkElement).DataContext = this;
I created a sample app on regarding this. Hopefully it reflects your situation correctly:
https://github.com/mikoskinen/uwpusercontrollistdp
If you clone the app and run it, you'll notice that the binding doesn't work. But if you uncomment the Datacontext = this line from UserControl, everything should work OK. Here's working code:
public PeopleLine()
{
this.InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
So, I have made my own subclass of UserControl, called ChildView (I really can't come up with a decent name), that I want to show inside a container in a window, I have many different kinds of these UserControls and the window must be capable of showing all of them. The UserControls have implemented my subclass like this:
<src:ChildView x:Class="(namespace).LoginView" [...]>
public partial class LoginView : ChildView
And I have tried to add it to my window like so:
<Grid x:Name="ViewHolder" Grid.Column="1" Grid.Row="1">
<src:ChildView DataContext="{Binding CurrentView}" />
</Grid>
private ChildView _currentView;
public ChildView CurrentView
{
get { return _currentView; }
set
{
if (_currentView == value)
return;
_currentView = value;
smLog.Trace("View set to {0}", value.GetType().Name);
NotifyPropertyChanged("CurrentView");
}
}
However, this does not work. Nothing is shown in my container when I set CurrentView. There are no error messages in the output that would indicate a problem with the binding. Other data bindings in the window works. I can use my ChildViews by specifying their classes directly in the XAML, i.e:
<Grid x:Name="ViewHolder" Grid.Column="1" Grid.Row="1">
<src:LoginView />
</Grid>
I've read some about dependency properties but I don't think I need one here? I did try to implement one anyway but it didn't seem to help, though I probably made some mistake, I couldn't quite wrap my head around it...
So I guess my question is; do I need a dependency property? If so, how do I implement it in this case? If not, what is the problem?
Changing the Child's DataContext won't matter, you're trying to change the control itself, not the data it's bound to. What you need to do is add a placeholder control that would contain the actual view. WPF has such a thing built in, take a look at ContentControl.
Change your grid so it'll containt a ContentControl instead of ChildView, and bind the view to the control's Content property
<Grid>
<ContentControl Content="{Binding CurrentView}"/>
</Grid>
I have a value converter with a property I would like to bind to, but the binding never happens, i.e. the dependency property in my value converter always is null.
Background: I want to bind an enum to a combo box but have control over the text that is being displayed.
I implemented the value converter like this:
public class EnumDisplayer : DependencyObject, IValueConverter
{
public static readonly DependencyProperty LocalizerProperty =
DependencyProperty.Register(
"Localizer", typeof(ILocalizer), typeof(EnumDisplayer),
new PropertyMetadata(default(ILocalizer), OnLocalizerChanged));
public ILocalizer Localizer
{
get { return (ILocalizer) GetValue(LocalizerProperty); }
set { SetValue(LocalizerProperty, value); }
}
private static void OnLocalizerChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
// ...
}
//...
}
And I bind it like this:
<UserControl.Resources>
<Common:EnumDisplayer x:Key="companyTypes"
Localizer="{Binding CompanyTypeEnumLocalizer}" />
<!-- ... -->
</UserControl.Resources>
My class is an adapted version of the EnumDisplayer.
I fail to understand, why OnLocalizerChanged is never called. Can anyone provide some insight?
(Stack Team correct me if I am wrong)... ValueConverters do not automatically support in binding and there are reasons...
They arent really something that the WPF framework is actively aware of, given that they dont lie on visual or logical tree.
They are used as part of inner markup extensions. This is a merky area. Unless they implement marrkup extensions on their own, they would be bound to.
Although there are ways..
Straightforward way is to use MultiBinding instead of single binding. The second binding will replace your converter's need to host a dependncy property.
http://www.codeproject.com/KB/WPF/AttachingVirtualBranches.aspx
I hope this helps.
I think this may be because the ResourceDictionary in which you are creating the instance is not part of the visual tree, so it cannot find the DataContext and the Binding therefore always returns null.
You may be able to get around this by giving your UserControl an x:Name attribute and then binding using ElementName and DataContext.PropertyName:
<UserControl x:Name="Root">
<UserControl.Resouces>
<Common:EnumDisplayer x:Key="companyTypes"
Localizer="{Binding DataContext.CompanyTypeEnumLocalizer, ElementName=Root}" />
</UserControl.Resouces>
</UserControl>
I found something about this issue for ASP, but it didn't help me much ...
What I'd like to do is the following: I want to create a user control that has a collection as property and buttons to navigate through this collection. I want to be able to bind this user control to a collection and display different controls on it (containing data from that collection).
Like what you had in MS Access on the lower edge of a form ...
to be more precise:
When I actually use the control in my application (after I created it), I want to be able to add multiple controls to it (textboxes, labels etc) between the <myControly> and </mycontrol>
If I do that now, the controls on my user control disappear.
Here is an example of one way to do what you want:
First, the code - UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty MyContentProperty =
DependencyProperty.Register("MyContent", typeof(object), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
public object MyContent
{
get { return GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
}
And the user control's XAML - UserControl1.xaml
<UserControl x:Class="InCtrl.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300" Name="MyCtrl">
<StackPanel>
<Button Content="Up"/>
<ContentPresenter Content="{Binding ElementName=MyCtrl, Path=MyContent}"/>
<Button Content="Down"/>
</StackPanel>
</UserControl>
And finally, the xaml to use our wonderful new control:
<Window x:Class="InCtrl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:InCtrl"
Title="Window1" Height="300" Width="300">
<Grid>
<me:UserControl1>
<me:UserControl1.MyContent>
<Button Content="Middle"/>
</me:UserControl1.MyContent>
</me:UserControl1>
</Grid>
</Window>
I'm having a hard time understanding your question, but I think what you're describing is an ItemsControl using DataTemplates to display the contents of (presumably) an ObservableCollection(T).
A UserControl may not be the best way to do this. You're wanting to add decorations around content, which is basically what Border does: it has a child element, and it adds its own stuff around the edges.
Look into the Decorator class, which Border descends from. If you make your own Border descendant, you should be easily able to do what you want. However, I believe this would require writing code, not XAML.
You might still want to make a UserControl to wrap the buttons at the bottom, just so you can use the visual designer for part of the process. But Decorator would be a good way to glue the pieces together and allow for user-definable content.
Here's a link to a built-in control (HeaderedContentControl) that does the same thing as the accepted answer except that it is an existing control in WPF since .Net 3.0