My goal is to manipulate the text-styles of my application via DependencyProperties. I got a diagram in which the texts are to be manipulated in size, fontfamily, color, etc. So I'd like to use an interface similar to a rich text editor like Word.
I'm using this code in my TextStyleVM http://shevaspace.blogspot.com/2006/12/i-have-some-fun-with-formattedtext_14.html
So I have a FontFamilyProperty and a Getter and Setter for it:
public static DependencyProperty FontFamilyProperty =
DependencyProperty.Register(
"FontFamily",
typeof(FontFamily),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
SystemFonts.MessageFontFamily,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(IsValidFontFamily));
public FontFamily FontFamily
{
get { return (FontFamily)base.GetValue(FontFamilyProperty); }
set { base.SetValue(FontFamilyProperty, value); }
}
Then there is a ToStyle method, which sets the style for the labels of the diagram, which are to be manipulated:
Style style = new Style();
Binding fontFamilyBinding = new Binding("FontFamily");
fontFamilyBinding.Source = this;
Setter fontFamilySetter = new Setter();
fontFamilySetter.Property = TextBlock.FontFamilyProperty;
fontFamilySetter.Value = fontFamilyBinding;
style.Setters.Add(fontFamilySetter);
return style;
Now this works for a TextBox. The textbox displays the current FontFamily, and if I enter a new, valid FontFamily like Arial into the textbox the FontFamily of the labels are changed.
However, what I'd like to have is a combobox, which displays the SystemFonts and where I can choose one FontFamily for my labels. However, the binding doesn't seem to work. Neither the system fonts nor the current fonts of the labels are displayed. The combobox is just empty.
This is my xaml:
<r:RibbonLabel Content="FontFamily" />
<!--these do not work-->
<r:RibbonComboBox SelectedItem="{Binding FontFamily}"/>
<r:RibbonComboBox ItemsSource="{Binding FontFamily}"/>
<!--this works-->
<r:RibbonTextBox Text="{Binding FontFamily}"/>
Now, I assume I have to set a different Setter for a ComboBox in the ToStyle Method. But I have no clue, which one. Maybe someting like this:
fontFamilySetter.Property = ComboBox.ItemSource;
However, if I set that Property, the TextBox still works. So is this the wrong place to start at? I'd also be grateful if someone could hint me to some documentation about using these Style-, Setter-, Binding-key-words, which are used in the ToStyle method, since this is somebody elses code I'm working with.
ItemsSource here expects a collection; e.g. Fonts.SystemFontFamilies
<ComboBox ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}"/>
Actually, here's a very nice link covering font selection:
http://www.hanselman.com/blog/LearningWPFWithBabySmashCustomerFeedbackAndAWPFFontComboBox.aspx
Scott Hanselman even shows how to render each font item in combobox with it's own font family.
Added per OP comment.
Here's an example of binding to dependency property. Property is named "MyFontFamily" to avoid collision with existing Window's property. Sorry, no Ribbon controls (I have bare 3.5 sp1).
Window1.xaml
<Window
x:Class="SimpleWpf.Window1"
x:Name="ThisWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical">
<ComboBox
SelectedItem="{Binding MyFontFamily, ElementName=ThisWindow}"
ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}"/>
<TextBlock
Text="Lazy dog jumper"
FontFamily="{Binding MyFontFamily, ElementName=ThisWindow}"
FontSize="24"/>
</StackPanel>
</Window>
Window1.xaml.cs
public partial class Window1 : Window
{
// ...
public static readonly DependencyProperty MyFontFamilyProperty =
DependencyProperty.Register("MyFontFamily",
typeof(FontFamily), typeof(Window1), new UIPropertyMetadata(null));
}
A just in Xaml solution with fonts sorted in alphabetical order:
<Window x:Class="WpfFontsComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="SortedFontsCollection" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}" >
<CollectionViewSource.SortDescriptions>
<ComponentModel:SortDescription PropertyName="Source" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<StackPanel>
<Label Content="42" FontFamily="{Binding ElementName=comboBoxFonts, Path=SelectedItem}" />
<ComboBox x:Name="comboBoxFonts" ItemsSource="{Binding Source={StaticResource SortedFontsCollection}}" />
</StackPanel>
</Window>
A great Font Combobox for WPF can be found here:
CodeProject.com: A XAML-Only Font ComboBox
It is pure XAML, can be just copied/pasted and even sorts the fonts properly. The article also describes nicely all the gotchas encountered and how to solve them.
Related
I'm totally lost with dependancy objects and binding. I often get things working without understanding why and how, this question is about knowing what should be happening.
I have a tiny user control with the following XAML
<Grid>
<Image Source="{Binding Icon}"></Image>
<TextBlock Text="{Binding Title}"></TextBlock>
</Grid>
My code behind has the following
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(Image), typeof(MenuItem));
public Image Icon
{
get { return (Image)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(String), typeof(MenuItem));
public string Title
{
get { return (string)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
My MainWindow is empty, other than a reference to this control and to the ResourceDictionary. In the MainWindow code behind, I set the DataContext in the constructor.
<Window x:Class="AppUi.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:AppUi.Control"
Title="">
//set up to Resource Dictionary - all binding and styling works fine :)
<loc:MenuItem Icon="{Binding MailIcon}" Title="{Binding MailTitle}"></loc:MenuItem>
In the ModelView for the MainWindow, I have the following 2 properties
private Image_mailIcon;
public Image MailIcon{
//inotifyproperty implementation
}
private string _mailTitle;
public string MailTitle{
//inotifyproperty implementation
}
My question is, in the UserControl, how do I do the binding? Since it's a user control within a MainWindow, and the MainWindow already has a datacontext, I think the UserControl will inherit the DataContext from the parent (From what I have read).
So, in my UserControl XAML, should I be binding to the MainWindow's Code Behind properties OR to the ViewModel properties?
In other words, should my UserControl be
<Grid>
<Image Source="{Binding MailIcon}"></Image>
<TextBlock Text="{Binding MailTitle}"></TextBlock>
</Grid>
OR
<Grid>
<Image Source="{Binding Icon}"></Image>
<TextBlock Text="{Binding Title}"></TextBlock>
</Grid>
Or, because I'm using a DataContext and the UserControl inherits, do I even need the Dependancy Properties at all?
You normally don't want to overwrite DataContext passed through visual tree so you can use either ElementName or RelativeSource binding inside UserControl to change binding context. The easiest way to achive this is give UserControl some name and use it ElementName binding
<UserControl ... x:Name="myUserControl">
<!-- ... -->
<Grid>
<Image Source="{Binding Icon, ElementName=myUserControl}"/>
<TextBlock Text="{Binding Title, ElementName=myUserControl}"/>
</Grid>
<!-- ... -->
</UserControl>
This way binding is DataContext independent. You can also create UserControl with assumption it will always work with only specific type of DataContext and then you just use Path from that view model type but then DataContext of that UserControl must always be of the view model it's designed for (mostly inherited through visual tree)
<UserControl ...>
<!-- ... -->
<Grid>
<Image Source="{Binding MailIcon}"/>
<TextBlock Text="{Binding MailTitle}"/>
</Grid>
<!-- ... -->
</UserControl>
I would also change type of Icon property from Image to ImageSource for example. You already have Image control inside your UserControl and you just want to bind its Source
in the UserControl, how do I do the binding? ... the UserControl will inherit the DataContext from the parent
That is correct, the UserControl will inherit the DataContext from the parent Window. Therefore you can data bind from the UserControl directly to the parent Window.DataContext. Please note that you would bind to whatever object has been set as the DataContext, regardless of whether that was the code behind or a separate view model class.
However, you don't have to data bind to the parent's DataContext object in this situation... you have other options. You could data bind to your own UserControl DependencyPropertys using a RelativeSource Binding like this:
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourUserControl}}}" />
You could also name your UserControl and reference its properties like this:
<TextBlock Text="{Binding Title, ElementName=YourUserControlName}" />
While this example seems to be more concise, don't overlook the first example, as RelativeSource is a useful and powerful friend to have.
should I be binding to the MainWindow's Code Behind properties OR to the ViewModel properties?
That's your choice... what do you want or need to data bind to? you just need to know that a direct data binding will use the auto set DataContext value, so if you don't want to use that, then you can just specify a different data source for the Binding as shown above.
Finally, regarding the need to use DependencyPropertys... you only need to declare them if you are developing a UserControl that needs to provide data binding abilities.
I used the following post to implement a datagrid bound to a list of dynamic objects
Binding DynamicObject to a DataGrid with automatic column generation?
The ITypedList method GetItemProperties works fine, a grid is displayed with all the columns I described.
I use a custom PropertyDescriptor and override the GetValue and SetValue methods as described in the above post, I also implement the TryGetMember and TrySetMember methods in the dynamic objects.
so basically I have a ComplexObject:DynamicCobject with a field Dictionary and a ComplexObjectCollection implementing ITypedList and IList.
This all works fine except when I bind the itemsSource of the DataGrid to the collection, the cells will show the SimpleObject type name and I actually want to implement a template to show the property Value of the SimpleObject in a text block.
I've used all sorts of methods to try and get the underlying SimpleObject but nothing works and I always get the ComplexObject for the row. I am using autogenerated columns and this always seems to produce a text column, this may be the problem but why cant I still get the underlying SimpleObject from somewhere in the cell properties?
Below would be my ideal solution but this does not work.
<Grid>
<Grid.Resources>
<DataTemplate x:Key="DefaultNodeTempate">
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Content}">
<ContentControl.Resources>
<DataTemplate DataType="local:SimpleObjectType">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</Grid.Resources>
<DataGrid ItemsSource="{Binding ElementName=mainWin, Path=DynamicObjects}">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultNodeTempate}" />
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
Any suggestions would be much appreciated.
Thanks
Kieran
So I found the solution was to do some work in the code behind.
In the AutoGeneratingColumn event create a DataTemplate with a content control and a custom template selector (I create the selector in Xaml and found it as a resource).
Create a binding for the ContentProperty of the ContentControl with e.PropertyName as the path
Create a new DataGridTemplateColumn and set the new columns CellTemplate to your new DataTemplate
replace e.Column with your new column and hey presto the cells datacontext bind with the dynamic property for that column.
If anyone has any refinement to this please feel free to share your thoughts.
Thanks
EDIT: As requested some sample code for my solution
Custom template selector:
public class CustomDataTemplateSelector : DataTemplateSelector
{
public List<DataTemplate> Templates { get; set; }
public CustomDataTemplateSelector()
: base()
{
this.Templates = new List<DataTemplate>();
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate template = null;
if (item != null)
{
template = this.Templates.FirstOrDefault(t => t.DataType is Type ? (t.DataType as Type) == item.GetType() : t.DataType.ToString() == item.GetType().ToString());
}
if (template == null)
{
template = base.SelectTemplate(item, container);
}
return template;
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="ParentControl">
<Grid.Resources>
<local:CustomDataTemplateSelector x:Key="MyTemplateSelector" >
<local:CustomDataTemplateSelector.Templates>
<DataTemplate DataType="{x:Type local:MyCellObject}" >
<TextBox Text="{Binding MyStringValue}" IsReadOnly="{Binding IsReadOnly}" />
</DataTemplate>
</local:CustomDataTemplateSelector.Templates>
</local:CustomDataTemplateSelector>
</Grid.Resources>
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="True" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" >
</DataGrid>
</Grid>
</Window>
Code behind:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
// Get template selector
CustomDataTemplateSelector selector = ParentControl.FindResource("MyTemplateSelector") as CustomDataTemplateSelector;
// Create wrapping content control
FrameworkElementFactory view = new FrameworkElementFactory(typeof(ContentControl));
// set template selector
view.SetValue(ContentControl.ContentTemplateSelectorProperty, selector);
// bind to the property name
view.SetBinding(ContentControl.ContentProperty, new Binding(e.PropertyName));
// create the datatemplate
DataTemplate template = new DataTemplate { VisualTree = view };
// create the new column
DataGridTemplateColumn newColumn = new DataGridTemplateColumn { CellTemplate = template };
// set the columns and hey presto we have bound data
e.Column = newColumn;
}
There may be a better way to create the data template, I have read recently that Microsoft suggest using a XamlReader but this is how I did it back then. Also I haven't tested this on a dynamic class but I'm sure it should work either way.
I have a ComboBox on a WPF window that is giving me some heartache, in that when first displayed, the first selection is rendered improperly. The ComboBox does not display text only; it displays an object of a type that descends from UserControl. Here's the XAML for the ComboBox itself:
<ComboBox Grid.Column="0"
Height="69"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
ItemsSource="{Binding Path=ViewChoices,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Mode=OneWay}"
Margin="10"
Name="ViewPicker"
SelectionChanged="ViewPicker_SelectionChanged"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center" />
As you can see, the ComboBox's ItemsSource is bound to a property of the UserControl that owns it called ViewChoices. The ViewChoices object is an ObservableCollection.
The contents of the ComboBox is set in code in the code behind, as the exact contents in the final code will be read from an XML file; the values are hard coded right now. Essentially, a CameraViewChoice object is created for each XML row read and added to the ViewChoices collection. This all happens in the UserControl's default constructor, after called InitializeComponent(). In the UserControl's Loaded event handler, I have code which sets the ComboBox's SelectedIndex property to 0.
The CameraViewChoice object is descended from UserControl. Here's the Xaml for this control:
<UserControl x:Class="CarSystem.CustomControls.CameraViewChoice"
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:cs="clr-namespace:CarSystem.CustomControls"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="50">
<Border BorderBrush="Black" BorderThickness="1">
<Grid>
<Image Opacity="0.35" Source="..." Stretch="Uniform" />
<Label Content="{Binding Path=Text}"
FontSize="18"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Border>
</UserControl>
Here's the code-behind for the CameraViewChoice object:
public partial class CameraViewChoice : UserControl {
public static readonly DependencyProperty AttachedCameraProperty = DependencyProperty.Register( "AttachedCamera", typeof( Camera ), typeof( CameraViewChoice ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender ) );
public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof( string ), typeof( CameraViewChoice ), new FrameworkPropertyMetadata( string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsRender ) );
public Camera AttachedCamera {
get { return (Camera) GetValue( AttachedCameraProperty ); }
set { SetValue( AttachedCameraProperty, value ); }
}
public string Text {
get { return (string) GetValue( TextProperty ); }
set { SetValue( TextProperty, value ); }
}
public CameraViewChoice() {
InitializeComponent();
}
This is all working fine but there's one problem. When the program starts running and the ComboBox displayed for the first time, the selected item is displayed all wrong. The label is blank and the CameraViewChoice control is displayed too large, so much so that the bottom of it is cut off. What I'm seeing is a blank CameraViewChoice object displayed without scaling to the ComboBox.
If I choose an item in the list, all of the choices in the list display properly and are sized properly & look fine after that, including the selected one. The problem is only when the window is first displayed.
Does anyone have any ideas about what's going on?
Tony
Edit:
I did some research on Google & MSDN Magazine and I found an article by Josh Smith about Data Templates. From there, I made the following changes to the XAML for my ComboBox:
<ComboBox Grid.Column="0"
Height="69"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
ItemsSource="{Binding Path=ViewChoices,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Mode=OneWay}"
Margin="10"
Name="ViewPicker"
SelectionChanged="ViewPicker_SelectionChanged"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<cs:CameraViewChoice Margin="10" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This is better as the items in the ComboBox do not change size, but the initial display is still too large. That is, it's getting cut off at the bottom. Further, the size of the selected item is consistently too large. So when you select one item in the list, it's displayed in the combobox partially clipped.
I want the choice displayed in the ComboBox with all of it fitting in side of it. Any suggestions for changes to the CombobBox's ItemTemplate?
In the Loaded event, provided you have at least 2 items, set the SelectedIndex to 1. After this, no matter how many items you have, call InvalidateMeasure and UpdateLayout on the ComboBox, then set the SelectedIndex to 0.
Here's what I think is happening.
You are using a standard ComboBox and dynamically adding UIElements to it. When the ComboBox is first displayed, there are no items, so it uses a default template. After you start adding UIElements to it, the renderer then performs it measuring and arranging. In essence, it's only learning what it should look like after the UIElements are created and inserted (but it still needed to know what to look like before that happened).
My suggestion would be to move from this development pattern to a more common methodology. Instead of creating UIElements on the fly, just create an ObservableCollection of CameraChoices (or whatever name would be appropriate). Typically this would be contained in a ViewModel.
Then instead of creating a UserControl and inserting it into the ItemsSource of the ComboBox, you'd be better served to create an ItemsTemplate (where you can use the UserControl) for the ComboBox. Alternatively, you can use a DataTemplate of the same type as the object in the ObservableCollection.
This will provide a more robust mechanism for displaying the list of items and provide you with a way to get to the raw data instead of having to deal with a UIElement when the SelectionChanged event is signaled.
I have another WPF databinding question... one that I haven't found an answer to anywhere, and this surprises me since it seems like it is very basic.
Essentially, I have a string in code behind that I would like to establish a two-way binding with with a textbox in my GUI. I thought it was a simple matter of creating a DependencyProperty in the code behind, and then tying it to the TextBox via a Source binding. The problem is, I can't get one or both parts right.
Here is my DependencyProperty definition from the code behind:
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register( "FilePath", typeof(string), typeof(Window1));
public string FilePath
{
get { return (string)GetValue(FilePathProperty); }
set { SetValue( FilePathProperty, value); }
}
And here is my XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ReportingInterface Test Application" Height="300" Width="536">
<Menu DockPanel.Dock="Top">
<MenuItem Name="menu_plugins" Header="File">
<MenuItem Header="Open">
<StackPanel Orientation="Horizontal">
<Label>File location:</Label>
<TextBox Name="text_filepath" Width="100" Text="{Binding Source=FilePath, Path=FilePath, Mode=TwoWay}"></TextBox>
<Button Margin="3" Width="20">...</Button>
</StackPanel>
</MenuItem>
</MenuItem>
</Menu>
The part I know is obviously wrong is the Binding part... I hate to waste people's time here with this question, but I honestly have come up short with every search (but now at least this request will populate subsequent google searches). :)
Thank you!
When you defined a binding in XAML, it binds to whatever is set as the DataContext for the object (or it's parent).
This typically means you'd set the DataContext of the Window to some class, and then the binding will work:
<TextBox Name="text_filepath" Width="100" Text="{Binding Path=FilePath, Mode=TwoWay}" />
You can fix this by adding, in the Window's constructor:
this.DataContext = this;
That will make the binding work against the window itself.
Alternatively, you can setup the binding to bind against a specific source object. If, in this case, you wanted to be able to use something else as the DataContext, but still want to bind to a Dependency Property defined in your Window, you could do:
<TextBox Name="text_filepath" Width="100" Text="{Binding Path=FilePath, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"></TextBox>
This works by telling the binding to find the first ancestor of type "Window", and bind it the "FilePath" property on that object.
For what it's worth, I would recommend looking into the M-V-VM pattern (Model, View, ViewModel)- essentially, what you do is have this class that serves as the DataContext for your XAML, and all your fun exposed properties/commands/what have you are exposed as public members of that class (called a ViewModel).
Here's a good overview webcast:
MVVM video
And here's another from MSDN mag:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
I have a wpf user control, which exposes a single custom dependency property. Inside the user control, a textblock binds to the value of the dp. This databinding works in all scenarios except when the data source is an object.
The minimal code necessary to reproduce this is:
this is the main part of the user control
<StackPanel Orientation="Horizontal">
<TextBlock Text="**SimpleUC** UCValue: "/>
<TextBlock Text="{Binding UCValue}"/>
</StackPanel>
and the user control code behind:
public SimpleUC()
{
InitializeComponent();
this.DataContext = this;
}
public string UCValue
{
get { return (string)GetValue(UCValueProperty); }
set { SetValue(UCValueProperty, value); }
}
public static readonly DependencyProperty UCValueProperty =
DependencyProperty.Register("UCValue", typeof(string), typeof(SimpleUC), new UIPropertyMetadata("value not set"));
this is the test window. I imported my project xml namespace as "custom"
<Window.Resources>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="20"/>
</Style>
</Window.Resources>
<StackPanel>
<StackPanel>
<TextBlock Text="This fails to bind:"/>
<custom:SimpleUC UCValue="{Binding SomeData}"/>
</StackPanel>
<StackPanel>
<TextBlock>The same binding on a regular control like Label</TextBlock>
<Label Content="{Binding SomeData}"/>
</StackPanel>
<Slider x:Name="sld" />
<StackPanel>
<TextBlock>However, binding the UC to another element value, like a slider works</TextBlock>
<custom:SimpleUC UCValue="{Binding ElementName=sld,Path=Value}"/>
</StackPanel>
</StackPanel>
and the test window code behind is:
public TestWindow()
{
InitializeComponent();
this.DataContext = this;
}
//property to bind to
public string SomeData { get { return "Hello S.O."; } }
When I turn on the diagnostic tracing on the TestWindow, it spits out the error "BindingExpression path error:
'SomeData' property not found on 'object' ''SimpleUC' (Name='')' ... "
The binding expression is the same as the one I used in the neighboring label and it worked fine. This behavior seems really bizarre to me. Can anyone shed some light?
You set DataContext of your SimpleUC to itself here
public SimpleUC()
{
InitializeComponent();
this.DataContext = this; // wrong way!
}
so when you use binding here
<custom:SimpleUC UCValue="{Binding SomeData}"/>
it searches property SomeData in control's data context which is set to this object because code in SimpleUC constructor overrides value of DataContext and it is not set to TestWindow object anymore as you expected. That's why your solution works - it doesn't affect DataContext which is inherited from window. Also you can keep this.DataContext = this; but set element where to search property explicitly like this (skipped irrelevant)
<Window ... Name="wnd1">
<custom:SimpleUC UCValue="{Binding SomeData, ElementName=wnd1}"/>
...
But my oppinion is that your variant from the answer looks more convenient to me, setting data context to this is not very good practice.
Hope it helps.
If you must use a UserControl, your
<TextBlock
Text="{Binding RelativeSource={RelativeSource Self},
Path=Parent.Parent.UCValue}"
/>
is an ok way to do it and
<TextBlock
Text="{Binding UCValue,
RelativeSource={RelativeSource FindAncestor,custom:SimpleUC,1}}"
/>
is better because you don't rely on the control hierarchy and possible instantiation order issues.
However I would recommend for this kind of situation that you use "custom controls" instead of "user controls". They take a little bit of getting used to, but they are much more powerful because their XAML is the template itself which means you can use TemplateBinding and {RelativeSource TemplatedParent}.
In any case, DataContext = this; is definitely to be avoided.