I have a custom WPF control that has a DependencyProperty:
public static readonly DependencyProperty DeferedVisibilityProperty;
private static void OnDeferedVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var control = (CircularProgressBar)d;
control.OnDeferedVisibilityChanged();
}
public bool DeferedVisibility {
get => (bool)GetValue(DeferedVisibilityProperty);
set => SetValue(DeferedVisibilityProperty, value);
}
private void OnDeferedVisibilityChanged() {
if(DeferedVisibility) {
VisualStateManager.GoToState(this, "Visible", true);
Visibility = Visibility.Visible;
} else {
VisualStateManager.GoToState(this, "Collapsed", true);
Visibility = Visibility.Collapsed;
}
}
Now we can set visibility without bool-to-visibility converter.
When using the Visibility DependencyProperty it does not work any more, so i want to mark it as obsolete.
I know you can mark members like this [Obsolete("Please use DeferedVisibility")].
So how can I mark the Visibility Property of the base class as obsolete?
Just do override the Visibility dependency property:
[Obsolete("Use something else")]
public new Visibility Visibility
{
get { return (Visibility)GetValue(VisibilityProperty); }
set { SetValue(VisibilityProperty, value); }
}
[Obsolete("Use something else")]
public new static readonly DependencyProperty VisibilityProperty =
DependencyProperty.Register(nameof(Visibility), typeof(Visibility), typeof(YourCustomControl), new PropertyMetadata(0));
But the problem I see, is that obsolete will only be marked in code behind, not in XAML(at least by me)
Related
I created an attached property for ListBox like this:
using ListBoxControl = System.Windows.Controls.ListBox;
namespace App.Ui.Views.AttachedProperties
{
public class ListBox
{
public static readonly DependencyProperty autoScrollProperty =
DependencyProperty.RegisterAttached(
"AutoScroll",
typeof(bool),
typeof(ListBoxControl),
new PropertyMetadata(false));
public static void SetAutoScroll(ListBoxControl element, bool value)
{
element.SetValue(autoScrollProperty, value);
if (value)
{
element.SelectionChanged += Element_SelectionChanged;
}
else
{
element.SelectionChanged -= Element_SelectionChanged;
}
}
public static bool GetAutoScroll(ListBoxControl element)
{
return (bool)element.GetValue(autoScrollProperty);
}
private static void Element_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBoxControl)sender;
listBox.ScrollIntoView(listBox.SelectedItem);
}
}
}
When I use a static True/False value in the xaml, it works fine:
<ListBox ap:ListBox.AutoScroll="True">
...
</ListBox>
But if I data bind to a property in my view model:
<ListBox ap:ListBox.AutoScroll="{Binding Path=Settings.EnableAutoScroll}">
...
</ListBox>
Then I get the following exception: A 'Binding' cannot be set on the 'SetAutoScroll' property of type 'ListBox'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Is this possible, or am I going to need to derive my own custom list box to accomplish this?
Problem in this line typeof(ListBoxControl). You should specify the name of the class where custom attached property seats.
I would recommend rename class from ListBox to ListBoxExtensions, also, make it static. Then you don't have to use alias ListBoxControl.
Your final code will look like:
public static class ListBoxExtensions
{
public static readonly DependencyProperty autoScrollProperty =
DependencyProperty.RegisterAttached(
"AutoScroll",
typeof(bool),
typeof(ListBoxExtensions),
new PropertyMetadata(false));
...
}
Edit:
OK, your code has another problem.
Remove attachment of the listener from setter (SetAutoScroll) and put this logic into dependency property callback.
public static class ListBoxExtensions
{
public static readonly DependencyProperty autoScrollProperty =
DependencyProperty.RegisterAttached(
"AutoScroll",
typeof(bool),
typeof(ListBoxExtensions),
new PropertyMetadata(false, AutoScrollChangedCallback));
public static void SetAutoScroll(ListBox element, bool value)
{
element.SetValue(autoScrollProperty, value);
}
public static bool GetAutoScroll(ListBox element)
{
return (bool)element.GetValue(autoScrollProperty);
}
private static void AutoScrollChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBox control = (ListBox)d;
if ((bool)e.NewValue)
{
control.SelectionChanged += Element_SelectionChanged;
}
else
{
control.SelectionChanged -= Element_SelectionChanged;
}
}
private static void Element_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBox)sender;
listBox.ScrollIntoView(listBox.SelectedItem);
}
}
I'm using MVVMCross in my crossplatform native Xamarin app. I seem to have a problem binding boolean properties in my custom control to boolean properties in my ViewModel. For example:
My custom control BarCodeTextBox.cs:
public sealed class BarCodeTextBox : TextBox
{
public BarCodeTextBox()
{
this.DefaultStyleKey = typeof(BarCodeTextBox);
}
public bool IsListeningForCodes
{
get { return (bool)GetValue(IsListeningForCodesProperty); }
set {
SetValue(IsListeningForCodesProperty, value);
if (value)
{
IsReadOnly = true;
PrefixElement.Visibility = Visibility.Collapsed;
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
}
else
{
IsReadOnly = false;
Focus(FocusState.Keyboard);
PrefixElement.Visibility = Visibility.Visible;
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
}
Text = string.Empty;
}
}
// Using a DependencyProperty as the backing store for IsListening. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsListeningForCodesProperty =
DependencyProperty.Register("IsListeningForCodes", typeof(bool), typeof(BarCodeTextBox), new PropertyMetadata(false));
The viewmodel of the page BoxCloseViewModel.cs:
public class BoxCloseViewModel : ViewModelBase
{
private bool m_isManualBoxCodeInput;
public bool IsManualBoxCodeInput
{
get { return m_isManualBoxCodeInput; }
set { SetProperty(ref m_isManualBoxCodeInput, value); }
}
}
The binding in BoxCloseView.xaml:
<Controls:BarCodeTextBox x:Name="BarCodeInput" Text="{Binding BoxCode, Mode=TwoWay}" IsListeningForCodes="{Binding IsManualBoxCodeInput, Mode=OneWay, Converter={StaticResource BoolToInverseBool}}"/>
When I change the value of IsManualBoxCodeInput in the ViewModel nothing happens in the control (IsListeningForCodes does not change). Things I checked:
The converter works perfectly (and removing it does not solve the issue). In fact the converter is called when the ViewModel property changes (I'm able to debug it).
When I change the value of IsListeningForCodes in the page's code behind, it works (the control shows the change).
When I do the exact same thing with a string property, everything works perfectly.
There are no binding errors in the Output log.
PropertyChangedEvent is fired correctly with IsManualBoxCodeInput property.
I've realized the same thing happened to another control with a boolean property which used to work, after migrating to MVVMCross no longer does.
Bindings don't call the setter property of a DependencyObject. If you want some code to execute when a binding changes you need to add it as a callback of the PropertyMetadata.
public bool IsListeningForCodes
{
get { return (bool)GetValue(IsListeningForCodesProperty); }
set { SetValue(IsListeningForCodesProperty, value); }
}
public static readonly DependencyProperty IsListeningForCodesProperty =
DependencyProperty.Register("IsListeningForCodes", typeof(bool), typeof(BarCodeTextBox),
new PropertyMetadata(false, OnIsListeningForCodesChanged));
static void OnIsListeningForCodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = (BarCodeTextBox)d;
instance.OnIsListeningForCodesChanged();
}
void OnIsListeningForCodesChanged()
{
if (IsListeningForCodes)
{
IsReadOnly = true;
PrefixElement.Visibility = Visibility.Collapsed;
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
}
else
{
IsReadOnly = false;
Focus(FocusState.Keyboard);
PrefixElement.Visibility = Visibility.Visible;
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
}
Text = string.Empty;
}
I have created a custom user control that is basically built of a grid, an image, and a textblock. I want to be able to set the source of the image and the text of the text block from properties of the user control. I have set up the following dependency properties; however, I want to know if there is a better way to set the child element's properties than the way below.
public sealed partial class LabeledTile : UserControl
{
public LabeledTile()
{
InitializeComponent();
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(LabeledTile), new PropertyMetadata(null,(o, args) => ((LabeledTile) o).ImageSourceChanged((ImageSource)args.NewValue)));
private void ImageSourceChanged(ImageSource newValue)
{
ImageElement.Source = newValue;
}
public ImageSource ImageSource
{
get { return (ImageSource) GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(LabeledTile), new PropertyMetadata(null, (o, args) => ((LabeledTile)o).TextChanged((String)args.NewValue)));
private void TextChanged(string newValue)
{
TextElement.Text = newValue;
}
public String Text
{
get { return (String) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
This is my first time using a dependency property and I want to make sure I am on the track. It seems like more work than should be required. Also, are callbacks really the best way to set the value?
Thank you in advance for the help.
I am trying to dynamically hide the Toggle Button on an Expander using a Property on my ViewModel set to the Visibility property. At least that is my thought. Is there a way to just alter that one setting on the ToggleButton control without having to do the ENTIRE template of the Toggle Button in my xaml?
You could use the Loaded event for the Expander and set up the Binding in the event handler or if you want a reusable way without code behind you could use an attached behavior.
The attached behavior finds the ToggleButton inside the Template and sets up the Binding to the attached property ToggleButtonVisibility.
Uploaded a sample app here: ExpanderToggleButtonVisibilityTest.zip
Use it like this
<Expander Name="expander"
behaviors:ExpanderBehavior.BindToggleButtonVisibility="True"
behaviors:ExpanderBehavior.ToggleButtonVisibility="{Binding YourVisibilityProperty}"
.../>
ExpanderBehavior
public class ExpanderBehavior
{
public static DependencyProperty BindToggleButtonVisibilityProperty =
DependencyProperty.RegisterAttached("BindToggleButtonVisibility",
typeof(bool),
typeof(ExpanderBehavior),
new PropertyMetadata(false, OnBindToggleButtonVisibilityChanged));
public static bool GetBindToggleButtonVisibility(Expander expander)
{
return (bool)expander.GetValue(BindToggleButtonVisibilityProperty);
}
public static void SetBindToggleButtonVisibility(Expander expander, bool value)
{
expander.SetValue(BindToggleButtonVisibilityProperty, value);
}
private static void OnBindToggleButtonVisibilityChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Expander expander = target as Expander;
if (expander.IsLoaded == true)
{
BindToggleButtonVisibility(expander);
}
else
{
RoutedEventHandler loadedEventHandler = null;
loadedEventHandler = new RoutedEventHandler(delegate
{
BindToggleButtonVisibility(expander);
expander.Loaded -= loadedEventHandler;
});
expander.Loaded += loadedEventHandler;
}
}
private static void BindToggleButtonVisibility(Expander expander)
{
ToggleButton headerSite = expander.Template.FindName("HeaderSite", expander) as ToggleButton;
if (headerSite != null)
{
Binding visibilityBinding = new Binding
{
Source = expander,
Path = new PropertyPath(ToggleButtonVisibilityProperty)
};
headerSite.SetBinding(ToggleButton.VisibilityProperty, visibilityBinding);
}
}
#region ToggleButtonVisibilityProperty
public static DependencyProperty ToggleButtonVisibilityProperty =
DependencyProperty.RegisterAttached("ToggleButtonVisibility",
typeof(Visibility),
typeof(ExpanderBehavior),
new PropertyMetadata(Visibility.Visible));
public static Visibility GetToggleButtonVisibility(Expander expander)
{
return (Visibility)expander.GetValue(ToggleButtonVisibilityProperty);
}
public static void SetToggleButtonVisibility(Expander expander, Visibility value)
{
expander.SetValue(ToggleButtonVisibilityProperty, value);
}
#endregion // ToggleButtonVisibilityProperty
}
I made own dependency property like this
public bool Visible
{
get
{
return (bool)GetValue(VisibleProperty);
}
set
{
System.Windows.Visibility v = value == true ? System.Windows.Visibility.Visible : System.Windows.Visibility.Hidden;
SetValue(VisibleProperty, value);
border.Visibility = v;
}
}
public static readonly DependencyProperty VisibleProperty = DependencyProperty.Register("Visible", typeof(bool), typeof(SpecialMenuItem), new UIPropertyMetadata(false));
I'm binding my app property to it, but no luck.
App property(app.xaml.cs) and binding:
public bool IsGamePlaying
{
get
{
return isGamePlaying;
}
set
{
isGamePlaying = value;
}
}
private bool isGamePlaying;
<my:SpecialMenuItem ... Visible="{Binding Path=IsGamePlaying, Source={x:Static Application.Current}}" />
When I'm in debug, it reads the IsGamePlaying property, but doesn't make attempt to set the Visible property
How make it work?
You cannot include any logic in your dependency property wrapper. WPF will, where possible, directly call GetValue/SetValue rather than use your CLR property. You should include any extraneous logic by using metadata in your dependency property. For example:
public static readonly DependencyProperty VisibleProperty = DependencyProperty.Register("Visible", typeof(bool), typeof(SpecialMenuItem), new FrameworkPropertyMetadata(false, OnVisibleChanged));
private static void OnVisibleChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
// logic here will be called whenever the Visible property changes
}
The CLR wrapper is merely a convenience for consumers of your class.