I have two questions about developing at Windows Phone:
I want to create custom control and be able to provide some extra XAML inside it. So I use ContentControl with ContentPresenter inside ControlTemplate.
<ContentControl>
<ControlTemplate>
<TextBlock Name="TextBlockControl" Text="Existing controls"/>
<ContentPresenter/>
</ControlTemplate>
</ContentControl>
It worked, but I can't access TextBlockControl inside ControlTemplate from code-behind. FindName always returns null.
Secondly, I want to provide attributes for Control, so I create DependencyProperty like this:
public string CustomText
{
get { return (string)GetValue(CustomTextProperty); }
set
{
SetValue(CustomTextProperty, value);
TextBlockControl.Text = value;
}
}
public static readonly DependencyProperty CustomTextProperty =
DependencyProperty.Register("CustomText", typeof(string), typeof(MyControl), null);
As you can see, I write TextBlockControl.Text = value; to set text for TextBlock inside of my Control. When I set static string - it works
<MyControl CustomText="Static Text"/>
But when I want to use Binding (e.g. for LocalizedStrings resource) - it doesn't work. Am i missing PropertyMeta Callback, or some IPropertyChanged inheritance? I have read tons of StackOverflow questions with the same issue, but nothing answered my questions.
the answer to the first question :
If you créate your custom-control, and you assign a template, you can Access to the elements in that template using :
[TemplatePart(Name = "TextBlockControl", Type = typeof(FrameworkElement))]
You have to put this attribute in order to tools like blend, know that the template for this custom-control has to have a textblock called TextBlockControl.Then from the control's OnApplyTemplate you should get a reference to it whit :
protected override void OnApplyTemplate()
{
_part1 = this.GetTemplateChild("TextBlockControl") as FrameworkElement;
base.OnApplyTemplate();
}
Related
I defined a custom loading spinner UserControl in a WPF UserContol library.
It has one dependency property:
public string SpinnerSourcePath { get => _spinner.Source.ToString(); set => _spinner.Source = (ImageSource)new ImageSourceConverter().ConvertFromString(value); }
public static readonly DependencyProperty SpinnerSourcePathProperty =
DependencyProperty.Register(nameof(SpinnerSourcePath), typeof(string), typeof(Spinner));
where _spinner is the Image.
(I tried it directly with ImageSource class but no dice)
The xaml looks like this:
<Image x:Name="_spinner" RenderTransformOrigin="0.5 0.5">
<SomeStyleToMakeItRotate.../>
</Image>
and I use it by defining it like:
<c:Spinner SpinnerSourcePath="/Test;component/_Resources/loading.png"/>
(The project name is Test, the Spinner control resides in a different project), nothing is displayed.
However, if I add the Source property directly in the Spinner definition:
<Image x:Name="_spinner" Source="/Test;component/_Resources/loading.png" RenderTransformOrigin="0.5 0.5">
<SomeStyleToMakeItRotate.../>
</Image>
it shows correctly...
This leads me to believe that the dependency property is wrong, but how ?
E1:
After trying to do the same steps on a different control it stopped working again.
This time I have a DP:
public static readonly DependencyProperty ValidationFunctionProperty =
DependencyProperty.Register(nameof(ValidationFunction), typeof(Func<string, bool>), typeof(ValidatedTextBox), new PropertyMetadata(OnAssignValidation));
public Func<string, bool> ValidationFunction {
get => (Func<string, bool>)GetValue(ValidationFunctionProperty);
set => SetValue(ValidationFunctionProperty, value);
}
private static void OnAssignValidation(DependencyObject d, DependencyPropertyChangedEventArgs e) {
Debugger.Break();
}
Control usage:
<c:ValidatedTextBox x:Name="valid"
Text="Test"
ValidationFunction="{Binding Validation, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource test}}"/>
The converter is just a Debugger.Break() and return original
And finally the RelativeSource control is my MainWindow
public MainWindow() {
InitializeComponent();
}
public Func<string,bool> Validation => (s) => true;
(There is a problem with the Text DP as well, but I think I can solve that one on my own)
E2
Ok Pro problem was the RelativePath pointing to UserControl but it was placed in a Window
Your dependency property declaration is wrong, because the get/set methods of the CLR property wrapper must call the GetValue and SetValue methods of the DependencyObject base class (and nothing else).
Besides that, the property should also use ImageSource as its type:
public static readonly DependencyProperty SpinnerSourceProperty =
DependencyProperty.Register(
nameof(SpinnerSource), typeof(ImageSource), typeof(Spinner));
public ImageSource SpinnerSource
{
get { return (ImageSource)GetValue(SpinnerSourceProperty); }
set { SetValue(SpinnerSourceProperty, value); }
}
The Image element in the UserControl's XAML would use the property like this:
<Image Source="{Binding SpinnerSource,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
I have the following scenario and hierarchy of XAML elements in my page:
<Page> ....
<StackPanel> ...
<Grid> ....
<StackPanel>
<uc:MyUserControl
ReferencedButton={Binding ElementName=RightButton} />
<Button x:Name="RightButton" Click="{x:Bind ViewModel.OpenFlyout}" Content="Clickme" />
</StackPanel>
......
Then the code behind from 'MyUserControl'
public UIElement ReferencedButton
{
get { return (UIElement)GetValue(ReferencedButtonProperty); }
set { SetValue(ReferencedButtonProperty, value); }
}
public static readonly DependencyProperty ReferencedButtonProperty =
DependencyProperty.Register(nameof(ReferencedButton), typeof(UIElement), typeof(MyUserControl), null);
So far so good, however I was expecting that in my code behind, the 'ReferencedButton' property would be filled with a reference to the 'RightButton' button. However it always returns null.
I even tried:
{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, ElementName=RightButton}
I know it is possible to bind the element, because I got the example from a DevExpress component, but still without any success.
I am following the suggestions/rules from the following docs:
Binding ElementName
XAML Namescopes
p.s: I know that I can pass the reference to the button in my code behind however I would like to do this through XAML itself.
It turns out that I needed to use a PropertyChangedCallback to make it work. So the solution is as below:
public static readonly DependencyProperty ReferencedButtonProperty=
DependencyProperty.Register(nameof(ReferencedButton),
typeof(UIElement),
typeof(MyUserControl),
new PropertyMetadata(default(UIElement),
new PropertyChangedCallback(PlacementCallBack)));
and in the code behind of my control I can access and set the value by implementing the PlacementCallBack like this:
public static void PlacementCallBack(object sender, DependencyPropertyChangedEventArgs e)
{
var myuserControl = sender as MyUserControl;
myuserControl.ReferencedButton = e.NewValue as UIElement;
}
The object DependencyPropertyChangedEventArgs contains two propeties NewValue and OldValue, they hold the old and new values of the previous object set.
I'm working on a "simple" case. I like to create a new custom control which implements a DependencyProperty. In the next step I like to create a binding for updating the properties in both directions. I've builded a simple sample for this case, but the binding doesn't seem to work. I've found a way for updating the DPControl's property by using the FrameworkPropertyMetadata, but I don't know whether it's also a good idea to use the OnPropertyChanged event.
HERE is my sample project:
My control contains simply a Label
<UserControl x:Class="WPF_MVVM_ListBoxMultiSelection.DPControl"
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:WPF_MVVM_ListBoxMultiSelection"
mc:Ignorable="d" Height="84.062" Width="159.641">
<Grid Margin="0,0,229,268">
<Label Content="TEST" x:Name="label" Margin="0,0,-221,-102"/>
</Grid>
</UserControl>
and implement a custom dependency property. Currently, I have also implemented the PropertyChanged method for the FramePropertyMetadata and set in this method the label's content, but I like to get it work in both directions.
public partial class DPControl : UserControl
{
public DPControl()
{
InitializeComponent();
}
public string MyCustomLabelContent
{
get { return (string)GetValue(MyCustomLabelContentProperty);}
set
{
SetValue(MyCustomLabelContentProperty, value);
}
}
private static void OnMyCustomLabelContentPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
DPControl control = (DPControl)source;
control.label.Content = e.NewValue;
}
public static readonly DependencyProperty MyCustomLabelContentProperty = DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
OnMyCustomLabelContentPropertyChanged
)
);
I use this control simply in a Window by:
<local:DPControl MyCustomLabelContent="{Binding MyLabelContent, Mode=TwoWay}" Margin="72,201,286,34"/>
MyLabelContent is a property in the ViewModel, which has implemented also the INotifyPropertyChanged interface.
public class ViewModel_MainWindow:NotifyPropertyChanged
{
private string _myLabelContent;
public string MyLabelContent
{
get { return _myLabelContent; }
set { _myLabelContent = value;
RaisePropertyChanged();
}
}...
So how can I get it work: Using the binding feature with my new control on custom properties.
In your UserControl:
<Label
Content="{Binding MyCustomLabelContent, RelativeSource={RelativeSource AncestorType=UserControl}}"
x:Name="label" Margin="0,0,-221,-102"/>
And get rid of that property-changed callback. All you need is the Binding.
I like to get it work in both directions
To make the dependency property two-way by default:
public static readonly DependencyProperty MyCustomLabelContentProperty =
DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
I omitted the unnecessary property change handler.
It can't usefully be two-way now, because Label.Content can't generate its own value. If you want your UserControl to set the value in its codebehind, that's easy:
MyCustomLabelContent = "Some arbitrary value";
If you did the binding like I showed you, that will update the Label in the UserControl XAML as well as the viewmodel property bound to the UserControl's dependency property.
If you want the XAML to set it, you'll need to
Lastly, this:
Margin="0,0,-221,-102"
Is not a good way to do layout. WPF layout with Grid, StackPanel, etc. is much easier and more robust.
Using VS2015 I'm adding some custom functionality to a TextBlock for a small app and, since I can't derive from TextBlock itself (it's sealed), I'm deriving from UserControl.
In my xaml file, I have
<TextBlock x:Name="innerText"/>
As the only element within the usercontrol.
In my code-behind, I have the following used for accessing the text:
public string Label
{
get { return innerText.Text; }
set {
if (value != innerText.Text)
{
innerText.Text = value;
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Label"));
}
}
}
This works great when I'm running my app. On other pages, I am able to add instances of the control and set the "Label" property correctly. Unfortunately, the value of the "Label" property doesn't carry through to the inner textbox within the designer itself.
How can I get the value to update in the designer? While not strictly necessary (as I said, at run-time it works fine), it would make layout in the designer much easier for me.
Update:
I also tried using a DependencyProperty, with the same issue. Run-time works great, design-time shows nothing.
public string Label
{
get { return GetValue(LabelProperty).ToString(); ; }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(AutoSizingText), new PropertyMetadata(string.Empty));
And then, in the xaml, I set the DataContext for the entire control:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
And tried to bind the Text value:
<TextBlock Text="{Binding Label}" />
I would recommend using a dependency property instead of relying on setting the innerText element's Text property. A dependency property will behave just like any other property on a control, including updating in design mode.
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(MyClassName), new PropertyMetadata(string.Empty));
And your XAML will look like this:
<UserControl x:Name="usr" ...>
...
<TextBlock Text="{Binding Label, ElementName=usr}" ... />
...
</UserControl>
Pro tip: Type propdp, then Tab, Tab to quickly create a dependency property.
Here's an example usage:
<local:MyUserControl Label="Le toucan has arrived"/>
Note: You do not need to set the DataContext to Self when using a dependency property, this will generally screw things up as the UserControl should not set it's own DataContext, the parent control should.
How to make an array of dependency object properties bindable for later binding as a static resource?
The code I have now, it seems that my DependencyObject bypasses the dependency property system...
I have the following class:
public class ValueMarker : DependencyObject
{
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register("Brush", typeof(Brush), typeof(ValueMarker), new FrameworkPropertyMetadata(Brushes.Aqua));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(ValueMarker), new FrameworkPropertyMetadata(0d));
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register("Offset", typeof(double), typeof(ValueMarker), new FrameworkPropertyMetadata(0d));
public Brush Brush
{
get { return (Brush)GetValue(BrushProperty); }
set { SetValue(BrushProperty, value); }
}
public double Offset
{
get { return (double)GetValue(OffsetProperty); }
set { SetValue(OffsetProperty, value); }
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
}
In the XAML, I create a resource array of these with some bindings like so:
<x:Array Type="my:ValueMarker" x:Key="plainMarks">
<my:ValueMarker Brush="Red" Offset="-5" Value="{Binding Path=...}" />
<my:ValueMarker Brush="Orange" Offset="-5" Value="{Binding Path=...}"/>
<my:ValueMarker Brush="Orange" Offset="-5" Value="{Binding Path=...}"/>
<my:ValueMarker Brush="Red" Offset="-5" Value="{Binding Path=...}" />
</x:Array>
While debugging the bindings, I've noticed that should I remove the setter for the DP, the XAML would display an error saying the property is missing. It was my understanding that XAML uses DP system to assign value thus enabling binding. In this case, if the XAML expect a 'normal' property, binding is impossible. Anyone can enlighten me on how can I make it work?
The reason you cannot bind your ValueMarkers here is because:
1.They are not in the VisualTree of your window/usercontrol.
2.They are not object of Type that can inherit DataContext even if they are not part of Visual Tree.
So in order to make your ValueMarkers bind to the properties in the View DataContext, first of all you will have to derive them from Freezable class like below:
public class ValueMarker : Freezable
{
//All your Dependency Properties comes here//
protected override Freezable CreateInstanceCore()
{
return new ValueMarker();
}
}
After doing this you can simply bind your object like below:
<my:ValueMarker x:Key="vm1" Brush="Orange" Offset="-5" Value="{Binding Path=Text1}"/>
Here Text1 is property in Windows/usercontrols DataContext
Then you can use this resource as:
<TextBox Text="{Binding Value, Source={StaticResource vm1}, StringFormat=F2}"/>
Similarly you can create resource for other ValueMarkers to use them in binding.
You will not be able to bind by creating the x:Array as simply x:Array not lies in visualtree and does not inherit DataContext hence its elements also have no access to it.
If you still want to use the collection whose element should support binding, then you will need to create your own collection class that should inherit Freezable and exposes DependancyProperty to capture the DataContext and set it on child elements also.