I would like to disable column reording in a control we have created that is derived from ListView. This control is called a SortableListView. I thought a dependency property would be the best way to implement this, but the ((SortableListVIew)source).View is returning null. Here is the code:
public class SortableListView : ListView
{
// ...lots of other properties here
public static readonly DependencyProperty AllowsColumnReorderProperty =
DependencyProperty.Register(
"AllowsColumnReorder",
typeof(bool),
typeof(SortableListView),
new UIPropertyMetadata(true, AllowsColumnReorderPropertyChanged));
public bool AllowsColumnReorder
{
get
{
return (bool)this.GetValue(AllowsColumnReorderProperty);
}
set
{
this.SetValue(AllowsColumnReorderProperty, value);
}
}
private static void AllowsColumnReorderPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
ViewBase vb = ((SortableListView)source).View;
if (vb != null)
{
((GridView)vb).AllowsColumnReorder = (bool)e.NewValue;
}
}
And the XAML:
<TableControls:SortableListView x:Name="QueueViewTable" Margin="0,0,0,0"
Style="{StaticResource ListViewStyle}"
ItemsSource="{Binding Path=QueueList}"
ItemContainerStyle="{StaticResource alternatingListViewItemStyle}"
AlternationCount="2"
SelectionMode="Single"
SortEnabled="False"
AllowsColumnReorder="false">
The trouble is that vb is always null so the method fails to set AllowsColumnReoder. I am quite sure that the cast is valid because the code originally looked like this in OnInitialized:
((GridView)this.View).AllowsColumnReorder = false;
...but I need to set the AllowsColumnReorder on a particular instance of the view so this code is no good.
Can anyone tell me what I'm doing wrong? Or is there a better way to set this property?
The View property of ListView is itself a dependency property that could change. It appears to not be set yet when you're setting your property?
You may have to override the View property in your sortable list view, so you can add a property change listener, and then apply your sort property when the view itself gets set?
in wpf, you can override a dependency property declared in a parent class, shown here: http://msdn.microsoft.com/en-us/library/ms754209.aspx
you'd override the metadata for the View property, and in the PropertyMetadata param you set there, you can pass a function like you are above for AllowsColumnReorderPropertyChanged
in that handler, you'd check to see if the new view is a gridview, and then set your property.
that way, the either order of AllowsColumnReorder or View getting set will properly set your property.
Related
I know that I can declare a new DependencyProperty as such:
public String PropertyPath
{
get { return (String)GetValue(PropertyPathProperty); }
set { SetValue(PropertyPathProperty, value); }
}
public static readonly DependencyProperty PropertyPathProperty =
DependencyProperty.Register(nameof(PropertyPath), typeof(String),
typeof(NotEmptyStringTextBox),
new FrameworkPropertyMetadata(PropertyPath_PropertyChanged));
protected static void PropertyPath_PropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var ctl = d as NotEmptyStringTextBox;
var binding = new Binding(ctl.PropertyPath)
{
ValidationRules = { new NotEmptyStringRule() },
// Optional. With this, the bound property will be updated and validation
// will be applied on every keystroke.
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
ctl.StringBox.SetBinding(TextBox.TextProperty, binding);
}
But then the UserControl can only recieve a string with the name of the property to bind, and bind to it.
What I would like is to be able to have the same kind of comportment as "classical" properties, which you can either bind to, or give a static value.
My usage would be a boolean that modifies the display state of a UserControl, either statically with a fixed value or dynamically with a binding, all depending on the use case.
Maybe the way I made my dependency Property in the first place is incorrect, but here is how I can use it:
<inputboxes:NotEmptyStringTextBox
Grid.Column="1"
PropertyPath="Name"/>
This will bind the "Name" property from the DataContext, but I can't use it with a raw string, as it will make a BindingExpression error: "property not found"
EDIT:
I now have tried the following:
public bool Test
{
get { return (bool)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(nameof(Test), typeof(bool),
typeof(DamageTemplateListEditableUserControl));
I declared this new property, but I still cannot bind anything to it, only raw values are accepted
You shouldn't create a new binding in the callback. In fact, you don't need any callback at all.
Rename the dependency property to something better like "Text" and just bind the Text property of StringBox to the current value of your dependency property like this:
<TextBox x:Name="StringBox"
Text="{Binding Text, RelativeSource={RelativeSource AncestorType=local:NotEmptyStringTextBox},
UpdateSourceTrigger=PropertyChanged}" />
You can then set or bind the dependency property as usual.
If you really want a "PropertyPath" property, it shouldn't be a dependency property that you can bind something to but rather a simple CLR property that you can set to a string that represents a name of a property to bind to.
This is for example how the DisplayMemberPath property of an ItemsControl is implemented.
I created a custom renderer for a Label. Now I wanted to manipulate with its focus state, so I created bindable property and event. It works fine when I change the bindable property from custom renderer like so: Element.IsFocused = true;
But when I change it from the view model, it affects XAML view, but for some reasons doesn't call the setter for this property. Here is the code:
In custom class:
public new static readonly BindableProperty IsFocusedProperty =
BindableProperty.Create(nameof(IsFocused), typeof(bool), typeof(FloatingEntry), false);
public new bool IsFocused
{
get { return (bool)GetValue(IsFocusedProperty); }
set
{
SetValue(IsFocusedProperty, value);
if (value) Focus();
}
}
In XAML:
IsFocused="{Binding PasswordEntryIsFocused}"
In View Model:
private bool _passwordEntryIsFocused;
public bool PasswordEntryIsFocused
{
get { return _passwordEntryIsFocused; }
set
{
SetProperty(ref _passwordEntryIsFocused, value);
}
}
In View Model in some method: PasswordEntryIsFocused = true;
It's not about new keyword, I tried without it.
And binding works, because I tried to bind it with a visual property, like IsVisible and it was working like it should, but setter is always called only from a custom renderer.
I think I may miss something in a context of bindable property work.
But when I change it from the view model, it affects XAML view, but for some reasons doesn't call the setter for this property.
Yes, that is a common mistake with WPF. The XAML generated code does not call the setter, instead it changes the bound dependency property immediately. You can't break on this event, unless you attach the PropertyChangedCallback event.
I'm working with a custom control that has several user-defined dependency properties. I'm running into the same issue described in this question.
My control is setting the default value of a custom dependency property in its constructor. When I use the control in a DataTemplate, the value set in the constructor is always used, even if I try to set it in XAML.
The answer to the linked question explains that the value set in the C# code has a higher priority, and a better approach would be to specify the default value in the dependency property's metadata.
In my case, I can't specify a default because the dependency property doesn't have a single default value that applies in all cases. The default values depend on another property, so I must look them up when the control is created and not when the property is registered.
Here's some code to help illustrate my problem:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty MyProperty =
DependencyProperty.Register(
"MyProperty",
typeof(int),
typeof(MyControl),
new FrameworkPropertyMetadata(
int.MinValue,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback("OnMyPropertyChanged")));
public MyControl() : base()
{
InitializeComponent();
this.MyProperty = GetDefaultPropertyValue();
}
public int MyProperty
{
get { return (int)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
private int GetDefaultPropertyValue()
{
// look up the appropriate default based on some other criteria
return 42;
// (in reality, the default value for "MyProperty"
// depends on the value of a "Mode" custom DependencyProperty.
// this is just hard coded for testing)
}
}
The XAML usage looks something like this:
<!-- View displays 4 (desired) -->
<local:MyControl MyProperty="4" />
<!-- View displays default of 42 (desired) -->
<local:MyControl />
<!-- View displays default of 42 (wanted 4) -->
<DataTemplate x:Key="MyTemplate">
<local:MyControl MyProperty="4"/>
</DataTemplate>
To summarize:
The desired behavior is that the value from XAML is used first. If the value is not specified in the XAML, then I would like to fallback to the default value set in the control's constructor.
If I just include the control directly in a view, I get the expected behavior. If the control is used inside a DataTemplate, then I always get the default set in the constructor (even when the data template explicitly sets another value).
Is there any other way to specify the default value when the control is used in a template? The only option I can think of is to break the control up into several separate but similar controls, each of which uses a default value that is registered with the dependency property (which removes the need to have the default set based on the a Mode property).
Setting the default value in OnApplyTemplate while adding a small check should solve this:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Only set the default value if no value is set.
if (MyProperty == (int)MyPropertyProperty.DefaultMetadata.DefaultValue)
{
this.MyProperty = GetDefaultPropertyValue();
}
}
Please note that although this will work, it's not ideal since setting the property's value by code will essentially clear any data bindings for this property. For example, the following binding will no longer work once you call MyProperty = 42 in code:
<local:MyControl MyProperty="{Binding SomeProperty}" />
It should be possible to set the value while maintaining any bindings by using SetCurrentValue(MyPropertyProperty, GetDefaultPropertyValue()); to modify the property instead of MyProperty = GetDefaultPropertyValue(), but I'm not sure I like that too much either.
A better solution
What I would do is introduce a new read-only property in addition to the existing one, which will act as a calculated property. For example:
private static readonly DependencyPropertyKey MyCalculatedPropertyPropertyKey =
DependencyProperty.RegisterReadOnly("MyCalculatedProperty", typeof(int), typeof(MyControl),
new PropertyMetadata(int.MinValue));
public static readonly DependencyProperty MyCalculatedPropertyProperty = MyCalculatedPropertyPropertyKey.DependencyProperty;
public int MyCalculatedProperty
{
get { return (int)GetValue(MyCalculatedPropertyProperty); }
private set { SetValue(MyCalculatedPropertyPropertyKey, value); }
}
private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyControl)d).MyCalculatedProperty = (int)e.NewValue;
}
public MyControl()
: base()
{
InitializeComponent();
MyCalculatedProperty = GetDefaultPropertyValue();
}
I'm puzzled by this probably trivial matter. I have my custom property on a descendant of DevExpresses class LayoutGroup (shouldn't make any difference IMO):
public class ExpandableLayoutGroup : LayoutGroup
{
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(Boolean), typeof(ExpandableLayoutGroup));
public Boolean IsExpanded
{
get { return (Boolean) GetValue(IsExpandedProperty); }
set
{
expandCheckBox.IsChecked = value;
SetValue(IsExpandedProperty, value);
}
}
}
Then I have XAML binding to a listbox (containing 2 items at this time) called listStates.
<ExpandableLayoutGroup x:Name="groupTool" IsExpanded="{Binding SelectedValue.IsTool, ElementName=listStates}" DataContext="{Binding Tool}" Header="Tool" View="GroupBox" />
The object list binding to listStates contains both properties (simplified):
public class State
{
public Boolean IsItemTool { get; private set; }
public Tool Tool { get; private set; }
}
Am I missing something obvious? Because I would expect that when I change listbox selection (single) it would also update IsExpanded property on a group. I have more subproperties of Tool binded (as apparent by DataContext="{Binding Tool}") and those update well. So the DataContext is changing correctly on listbox selection. Except this one property IsExpanded (that should expand/collapse layoutgroup).
What am I doing wrong, and how to make it so, that when listbox changes, IsExpanded binding is polled to get value from IsTool, and will set it (depending on selection).
Getters and setters of dependency properties must contain only GetValue and SetValue calls, because there're two ways to get and set their values: using getters and setters and using DependencyProperty or DependencyPropertyKey. This is why code in your setter is not executed (bindings don't use them for dependency properties). If you want some code to be executed when the value is changed, specify it as a PropertyChangedCallback callback in PropertyMetadata.
See PropertyChangedCallback Delegate on MSDN.
The setter for a dependency property must only set the value for it's underlying type. The .NET internals reserve the right to call SetValue() directly (and in my experience WPF usually does, while Silverlight uses the setter that you've defined).
Refer to the "Implications for Custom Dependency Properties" section of XAML Loading and Dependency Properties for details
I've created a UserControl which is essentially a button. It's got an Image and a Label on it and I've created two properties to set the Image's source and the Label's text like so:
public ImageSource Icon
{
get { return (ImageSource)this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); icon.Source = value; }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(NavigationButton));
public string Text
{
get { return (string)this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); label.Content = value; }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(NavigationButton));
However, when I've added the control to my Page, the controls wont respond to any properties I set in XAML, e.g. <controls:MusicButton Icon="/SuCo;component/Resources/settings.png/> does nothing.
What am I doing wrong?
CLR properties that wrap dependency properties should never have any logic other than calling GetValue and SetValue. That is because they may not even be called. For example, the XAML compiler will optimize by calling GetValue/SetValue directly rather than using your CLR property.
If you need to execute some logic when a dependency property is changed, use metadata:
public ImageSource Icon
{
get { return (ImageSource)this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(NavigationButton), new FrameworkPropertyMetadata(OnIconChanged));
private static void OnIconChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
//do whatever you want here - the first parameter is your DependencyObject
}
EDIT
In my first answer, I assumed your control's XAML (be it from a template or directly in a UserControl) is correctly hooked up to the properties. You haven't showed us that XAML, so it was perhaps an incorrect assumption. I'd expect to see something like:
<StackPanel>
<Image Source="{Binding Icon}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
And - importantly - your DataContext must be set to the control itself. You can do this in various different ways, but here is a very simple example of setting it from the code behind:
public YourControl()
{
InitializeComponent();
//bindings without an explicit source will look at their DataContext, which is this control
DataContext = this;
}
Have you tried setting the text property as well? The source of an image may just be wrong. Text is much more straight forward.
Also, in your example, you missed a quotation mark. So if it's copied from your real code, you may want to check that.
Barring those minor admittedly unlikely causes for your problem, I'd suggest setting the properties in code to check whether that has any effect. If it has, then you should really check your XAML.
Since you haven't posted the rest of your code, I can't really tell if you have problems somewhere else that might affect the control.
And yes, I know I'm not very helpful, but I've been working with WPF for only a little while. Hope it helps anyway.