I'm working in a WPF project, it has a numerous number of user control.
for some forms , when the user close the container tab, a confirmation close is shown up, for other forms they are just closed.
so I use the
FrmAccounts FrmAcc = new FrmAccounts {Tag = "showConfirmClose"};
to decide what tab I need to close.
and in the closing event I check if the tag is set to showConfirmClose to show the confirmation message. But I don't like using Tag it's not good in C#, also I thought, what if I want to send more data (the only solution will be to comma separating them in the Tag and Split them, but this is worse).
I can't find a good, performant way to accomplish such task:
if this is possible:
FrmAccounts FrmAcc = new FrmAccounts {new{ShowConfirmClose= true }};
Attached property
You can use an AttachedProperty to store the additional information for your objects. However, these object have to be DependencyObjects, otherwise it won't work.
Here is an example.
Define your attached property in a separate class (it can be a simple static class or a DependencyObject):
namespace YourNamespace
{
public static class CloseConfirmation
{
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached(
"IsActive",
typeof(bool),
typeof(CloseConfirmation));
public static bool GetIsActive(DependencyObject obj)
{
return (bool)obj.GetValue(HiddenProperty);
}
public static void SetIsActive(DependencyObject obj, bool value)
{
obj.SetValue(HiddenProperty, value);
}
}
}
In XAML, set the attached property value for your objects (WPF Windows, Pages and other DependencyObjects):
<FrmAccounts xmlns:yns="YourNamespace"
yns:CloseConfirmation.IsActive="true">
<!-- Content -->
</FrmAccounts>
You can use this attached property in your XAML definition, e.g. in triggers.
You can also access its value in code-behind:
FrmAccounts frm = new FrmAccounts();
CloseConfirmation.SetIsActive(frm, true);
var isActive = CloseConfirmation.GetIsActive(frm); // true
You can create an attached property not only for a simple type like bool or int, but also for any custom type. So use your own container class if you need to transfer a lot of data.
Tag object
Using Tags is fully OK in .NET. Virtually any UI type has this property, so that you can attach objects of any type to your UI elements. However, these types cannot be anonymous types. You have to create your own container type:
class Container
{
public bool IsConfirmationNeeded { get; }
public IEnumerable<IItem> MyData { get; } // any data you need to pass
}
FrmAccounts frm = new FrmAccounts();
frm.Tag = new Container();
var container = (Container)frm.Tag;
var confirmation = container.IsConfirmationNeeded; // false by default
Related
I have a custom control with following code:
public partial class TableSelectorControl : UserControl
{
private Brush _cellHoverBrush = new SolidColorBrush(Colors.CadetBlue) { Opacity = 0.3 };
public static readonly DependencyProperty ActiveSelectionProperty =
DependencyProperty.Register("ActiveSelection", typeof(TableSelectorSelection),
typeof(TableSelectorControl));
public TableSelectorSelection ActiveSelection
{
get => (TableSelectorSelection)GetValue(ActiveSelectionProperty);
set
{
SetValue(ActiveSelectionProperty, value);
_cellHoverBrush = value.HoverBrush;
}
}
}
As you can see, I'm trying to set _cellHoverBrush on each ActiveSelectionProperty update, which is done from ViewModel. Binding works well and the ActiveSelectionProperty seemes to change, but the setter is not firing. I surely can use a FrameworkProperyMetadata, but I don't want _cellHoverBrush to become static, the idea is to change it with respect to selected ActiveSelection. How can I achieve this?
I can provide more info, if needed.
There are two types of properties in WPF: .NET Framework properties and dependency properties (which are specific for WPF). Each dependency property has associated a .Net Framework property, but this property is only a wrapper over WPF dependencies properties. This is done to standardize the way we work with properties in WPF. When a dependency property is used in bindings from .xaml files, the WPF framework will not use the .Net wrapper property to get or set the value. This is why, it's not indicated to use other code than GetValue and SetValue in your .NET wrapper property.
For what you need, you should use PropertyChangedCallback, like in the example below:
public partial class TableSelectorControl : UserControl
{
private Brush _cellHoverBrush = new SolidColorBrush(Colors.CadetBlue) { Opacity = 0.3 };
public static readonly DependencyProperty ActiveSelectionProperty =
DependencyProperty.Register("ActiveSelection", typeof(TableSelectorSelection),
typeof(TableSelectorControl), new PropertyMetadata(new PropertyChangedCallback(OnActiveSelectionChanged)));
public TableSelectorSelection ActiveSelection
{
get => (TableSelectorSelection)GetValue(ActiveSelectionProperty);
set => SetValue(ActiveSelectionProperty, value);
}
private static void OnActiveSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tableSelCtrl = d as TableSelectorControl;
if (tableSelCtrl != null)
{
tableSelCtrl._cellHoverBrush = (e.NewValue as TableSelectorSelection)?.HoverBrush;
}
}
}
Using the PropertyChangedCallback of FrameworkPropertyMetadata doesn't necessarily mean you need to make your field static. Your handler method will get a reference to the instance that is invoking it which you can then modify - you will need to cast it to your type first though.
The PropertyChanged walkthrough on this page shows one way you might do it.
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-property-callbacks-and-validation
There are default values you can set for some of window properties in WPF. For example you can set a Canvas' Width or a TextBlock's Text in XAML and then your app can change them by accessing the controls. Are there any ways of setting those values to how they were declared in XAML without saving them yourself? Are they kept somewhere so that you can access them at runtime?
My question is if there is maybe a function or some hacky way of setting all the values to default somewhere in the wpf api?
No, there isn't.
You better close the window and create a new one. Or you will have to "manually" reset all values by writing your own custom code that does this.
The framework cannot be supposed to know when you want to reset the values of your control properties without you specifying it somewhere. And the way to specify it is to write code.
I think that there is no way for WPF to know what your default values are but you can create your own attribute that contains information about default value and create method that will set that value.
First we need attribute that will hold information about default value of given property:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class DefaultAttribute : Attribute
{
public object Value;
public DefaultAttribute(object value)
{
this.Value = value;
}
}
Given this attribute we can easy declare default values for our properties like this:
class ViewModel
{
[Default("Noname")]
public string Name { get; set; }
public int Age { get; set; }
}
Then if you want to apply defaults to your view model you need some kind of method that will do that for you:
private static void ApplyDefaults(object model)
{
PropertyInfo[] properties = model.GetType().GetProperties();
foreach (var property in properties)
{
DefaultAttribute defaultAttr = property.GetCustomAttribute<DefaultAttribute>();
if (defaultAttr != null)
property.SetValue(model, defaultAttr.Value);
}
}
Example program with usage:
public static void Main()
{
var vm = new ViewModel();
Console.WriteLine($"Start - {vm}");
ApplyDefaults(vm);
Console.WriteLine($"ApplyDefaults - {vm}");
vm.Name = "Damian";
vm.Age = 23;
Console.WriteLine($"After using setters - {vm}");
ApplyDefaults(vm);
Console.WriteLine($"After ApplyDefaults - {vm}");
}
Output for the program is:
Start - - 0
ApplyDefaults - Noname - 0
After using setters - Damian - 23
After ApplyDefaults - Noname - 23
I've just checked that .NET have DefaultValue Attribute which you can use in similar way.
I am working on some custom (inherited) controls with custom properties for which I have added design-time support after proper attribute decoration.
All works well but my problem is that the auto-generated code in the *.Designer.cs file where the control is used, has a particular order that it sets the various properties in (both base and new properties). This order looks like it is alphabetical w.r.t. the property name.
So the auto generated code looks like this:
//
// myTabPage1
//
this.myTabPage1.Identifier = "ID";
this.myTabPage1.Name = "myTabPage1";
this.myTabPage1.Size = new System.Drawing.Size(294, 272);
this.myTabPage1.Text = "TTT";
Where I would have liked it to look like this:
//
// myTabPage1
//
this.myTabPage1.Name = "myTabPage1";
this.myTabPage1.Size = new System.Drawing.Size(294, 272);
this.myTabPage1.Text = "TTT";
this.myTabPage1.Identifier = "ID";
The reason I need this is because setting the Identifier property affects Text which is then reverted back to its design-time value, annulling the effect of setting the Identifier.
There are of course simple workarounds (the simplest of which is not to set the Text property which work well) but it would be nice if this was not a design-time "worry", as there is extensive usage of this design pattern in many inherited control types and their instances.
It is also helpful to set Text to identify the controls on the form designer (Identifier has no effect on Text at design-time).
No, you can't affect serialization order. This is otherwise a common problem with a general solution, implement the ISupportInitialize interface. Your BeginInit() method is called just before the designer starts assigning properties, you'd typically set a bool variable that ensures that property setters don't have unintended side-effects. Your EndInit() method is called when it is done and all properties have a value, you'd set the variable back to false and do whatever you need to do to use the values.
Your question isn't specific enough about the control with the problem, but a possible implementation could look like this:
public partial class CustomControl : UserControl, ISupportInitialize {
public CustomControl() {
InitializeComponent();
}
private bool initializing;
private string id = "";
public string ID {
get { return id; }
set { id = value;
if (!initializing) label1.Text = value;
}
}
[Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text {
get { return base.Text; }
set {
base.Text = value;
if (!initializing && !this.DesignMode) label1.Text = value;
}
}
public void BeginInit() {
initializing = true;
}
public void EndInit() {
initializing = false;
label1.Text = ID;
}
}
Also note the [DesignerSerializationVisibility] attribute in this code, when you use Hidden then the property doesn't get serialized at all. That could be a simple solution to your problem.
i am trying to use Property Editor for my user control but it doesn't work.
if i set the property in the form load if works, but if i want to use the property editor it don't save my changes (when i click again in the property editor it comes clear)
this is how i define the property in my user control:
private List<Field> _searchField;
public List<Field> SearchField
{
get { return _searchField ?? (_searchField = new List<Field>()); }
}
You need to apply DesignerSerializationVisibility attribute to your property with DesignerSerializationVisibility.Content.
This tells the code generator to produces code for the contents of the object, rather than for the object itself. It helps in code generation for types other than primitive types.
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<int> SearchField { get { return _searchField ?? (_searchField = new List<int>()); } }
I'm trying to create a GUI (WPF) Library where each (custom) control basically wraps an internal (third party) control. Then, I'm manually exposing each property (not all of them, but almost). In XAML the resulting control is pretty straightforward:
<my:CustomButton Content="ClickMe" />
And the code behind is quite simple as well:
public class CustomButton : Control
{
private MyThirdPartyButton _button = null;
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public CustomButton()
{
_button = new MyThirdPartyButton();
this.AddVisualChild(_button);
}
protected override int VisualChildrenCount
{
get
{ return _button == null ? 0 : 1; }
}
protected override Visual GetVisualChild(int index)
{
if (_button == null)
{
throw new ArgumentOutOfRangeException();
}
return _button;
}
#region Property: Content
public Object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
"Content", typeof(Object),
typeof(CustomButton),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeContent))
);
private static void ChangeContent(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as CustomButton).UpdateContent(e.NewValue);
}
private void UpdateContent(Object sel)
{
_button.Content = sel;
}
#endregion
}
The problem comes after we expose MyThirdPartyButton as a property (in case we don't expose something, we would like to give the programmer the means to use it directly). By simply creating the property, like this:
public MyThirdPartyButton InternalControl
{
get { return _button; }
set
{
if (_button != value)
{
this.RemoveVisualChild(_button);
_button = value;
this.AddVisualChild(_button);
}
}
}
The resulting XAML would be this:
<my:CustomButton>
<my:CustomButton.InternalControl>
<thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>
And what I'm looking for, is something like this:
<my:CustomButton>
<my:CustomButton.InternalControl Content="ClickMe" />
But (with the code I have) its impossible to add attributes to InternalControl...
Any ideas/suggestions?
Thanks a lot,
--
Robert
WPF's animation system has the ability to set sub-properties of objects, but the XAML parser does not.
Two workarounds:
In the InternalControl property setter, take the value passed in and iterate through its DependencyProperties copying them to your actual InternalControl.
Use a build event to programmatically create attached properties for all internal control properties.
I'll explain each of these in turn.
Setting properties using the property setter
This solution will not result in the simplified syntax you desire, but it is simple to implement and will probably solve the main problem with is, how to merge values set on your container control with values set on the internal control.
For this solution you continue to use the XAML you didn't like:
<my:CustomButton Something="Abc">
<my:CustomButton.InternalControl>
<thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>
but you don't actually end up replacing your InternalControl.
To do this, your InternalControl's setter would be:
public InternalControl InternalControl
{
get { return _internalControl; }
set
{
var enumerator = value.GetLocalValueEnumerator();
while(enumerator.MoveNext())
{
var entry = enumerator.Current as LocalValueEntry;
_internalControl.SetValue(entry.Property, entry.Value);
}
}
}
You may need some additional logic to exclude DPs not publically visible or that are set by default. This can actually be handled easily by creating a dummy object in the static constructor and making a list of DPs that have local values by default.
Using a build event to create attached properties
This solution allows you to write very pretty XAML:
<my:CustomButton Something="Abc"
my:ThirdPartyButtonProperty.Content="ClickMe" />
The implementation is to automatically create the ThirdPartyButtonProperty class in a build event. The build event will use CodeDOM to construct attached properties for each property declared in ThirdPartyButton that isn't already mirrored in CustomButton. In each case, the PropertyChangedCallback for the attached property will copy the value into the corresponding property of InternalControl:
public class ThirdPartyButtonProperty
{
public static object GetContent(...
public static void SetContent(...
public static readonly DependencyProperty ContentProperty = DependencyProperty.RegisterAttached("Content", typeof(object), typeof(ThirdPartyButtonProperty), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((CustomButton)obj).InternalControl.Content = (object)e.NewValue;
}
});
}
This part of the implementation is straightforward: The tricky part is creating the MSBuild task, referencing it from your .csproj, and sequencing it so that it runs after the precompile of my:CustomButton so it can see what additional properties it needs to add.