How to Expose a DependencyProperty of a Control nested in a UserControl? - wpf

I'm trying to bind an Image down from a Window into a UserControl 'Display' thats inside a UserControl 'DisplayHandler'. Display has a DependancyProperty 'DisplayImage'.
This is Similar to this, but their answers didn't help with my problem.
DisplayHandler also should have the Property 'DisplayImage' and pass down the Binding to Display. But Visual Studio doesn't allow me to register a DependancyProperty with the same name twice. So I tried to not register it twice but only to reuse it:
window
<my:DisplayHandler DisplayImage=
"{Binding ElementName=ImageList, Path=SelectedItem.Image}" />
DisplayHandler
xaml
<my:Display x:Name="display1"/>
cs
public static readonly DependencyProperty DisplayImageProperty =
myHWindowCtrl.DisplayImageProperty.AddOwner(typeof(DisplayHandler));
public HImage DisplayImage {
get { return (HImage)GetValue(DisplayImageProperty); }
set { SetValue(DisplayImageProperty, value); }
}
public HImage DisplayImage /*alternative*/ {
get { return (HImage)display1.GetValue(Display.DisplayImageProperty); }
set { display1.SetValue(Display.DisplayImageProperty, value); }
}
**neither of the 2 properties worked out.*
Display
public HImage DisplayImage {
get { return (HImage)GetValue(DisplayImageProperty); }
set { SetValue(DisplayImageProperty, value); }
}
public static readonly DependencyProperty DisplayImageProperty =
DependencyProperty.Register("DisplayImage", typeof(HImage), typeof(Display));
I have been thinking a Control goes up the Tree and looks for its Property if its own Value is not defined. ->reference
So it should work somehow...
I made some attempts with Templating and A ContentPresenter because that worked for the ImageList(ImageList also contains the Display), but I couldn't get it to bind the value like for an ListBoxItem.

The AddOwner solution should be working, but you have to add a PropertyChangedCallback that updates the embedded control.
public partial class DisplayHandler : UserControl
{
public static readonly DependencyProperty DisplayImageProperty =
Display.DisplayImageProperty.AddOwner(typeof(DisplayHandler),
new FrameworkPropertyMetadata(DisplayImagePropertyChanged));
public HImage DisplayImage
{
get { return (Image)GetValue(DisplayImageProperty); }
set { SetValue(DisplayImageProperty, value); }
}
private static void DisplayImagePropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var dh = obj as DisplayHandler;
dh.display1.DisplayImage = e.NewValue as HImage;
}
}

Related

Message box/dialog/pop-up approach in MVVM

I want to find a way to implement different forms of modals in MVVM WPF application. Like dialogs with returning results, message boxes or modal sub-windows with some controls inside.
Can you give me an advice about an efficient and modern approach for it?
I rarely find much use for anything other than a confirmation request. "Do you really want to delete that?" kind of thing.
Things popping up and asking for extra input just aren't super useful in my experience.
To my mind though, what you're doing is splitting your code. There is code up to showing the dialog. There is then code happens if the user clicks OK or Yes or selects a thingummajig in the dialog.
I split these into separate pieces of code. So there is not necessarilly a need to stop code running. It's in a separate method ( or command ) which is only run if the user hits the right button.
My first approach uses a control which itself has no UI. It exists just to get something encapsulated into the view.
namespace UserInput
{
public class ConfirmationRequestor : Control, ICommandSource
{
public bool? ShowConfirmDialog
{
get
{
return (bool?)GetValue(ShowConfirmDialogProperty);
}
set
{
SetValue(ShowConfirmDialogProperty, value);
}
}
public static readonly DependencyProperty ShowConfirmDialogProperty =
DependencyProperty.Register("ShowConfirmDialog",
typeof(bool?),
typeof(ConfirmationRequestor),
new FrameworkPropertyMetadata(null
, new PropertyChangedCallback(ConfirmDialogChanged)
)
{ BindsTwoWayByDefault = true }
);
private static void ConfirmDialogChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool?)e.NewValue != true)
{
return;
}
ConfirmationRequestor cr = (ConfirmationRequestor)d;
Window parent = Window.GetWindow(cr) as Window;
MessageBoxResult result = MessageBox.Show(parent, cr.Message, cr.Caption, cr.MsgBoxButton, cr.MsgBoxImage);
if (result == MessageBoxResult.OK || result == MessageBoxResult.Yes)
{
if (cr.Command != null)
{
cr.Command.Execute(cr.CommandParameter);
}
}
cr.SetValue(ShowConfirmDialogProperty, false);
}
public MessageBoxButton MsgBoxButton
{
get { return (MessageBoxButton)GetValue(MsgBoxButtonProperty); }
set { SetValue(MsgBoxButtonProperty, value); }
}
public static readonly DependencyProperty MsgBoxButtonProperty =
DependencyProperty.Register("MsgBoxButton",
typeof(MessageBoxButton),
typeof(ConfirmationRequestor),
new PropertyMetadata(MessageBoxButton.OK));
public MessageBoxImage MsgBoxImage
{
get { return (MessageBoxImage)GetValue(MsgBoxImageProperty); }
set { SetValue(MsgBoxImageProperty, value); }
}
public static readonly DependencyProperty MsgBoxImageProperty =
DependencyProperty.Register("MsgBoxImage",
typeof(MessageBoxImage),
typeof(ConfirmationRequestor),
new PropertyMetadata(MessageBoxImage.Warning));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption",
typeof(string),
typeof(ConfirmationRequestor),
new PropertyMetadata(string.Empty));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string),
typeof(ConfirmationRequestor),
new PropertyMetadata(string.Empty));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(ConfirmationRequestor), new UIPropertyMetadata(null));
}
}
I have a viewmodel designed to go with this which is exposed as a property on my window viewmodel. That viewmodel and the control encapsulate the confirmer functionality.
namespace UserInput
{
public class ConfirmationRequestorVM : INotifyPropertyChanged
{
private string caption;
public string Caption
{
get { return caption; }
set
{
caption = value;
NotifyPropertyChanged();
}
}
private string message;
public string Message
{
get { return message; }
set
{
message = value;
NotifyPropertyChanged();
}
}
private MessageBoxButton msgBoxButton;
public MessageBoxButton MsgBoxButton
{
get { return msgBoxButton; }
set
{
msgBoxButton = value;
NotifyPropertyChanged();
}
}
private MessageBoxImage msgBoxImage;
public MessageBoxImage MsgBoxImage
{
get { return msgBoxImage; }
set
{
msgBoxImage = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Usage
In the relevant view
<input:ConfirmationRequestor
ShowConfirmDialog="{Binding ShowConfirmation, Mode=TwoWay}"
MsgBoxImage="{Binding confirmer.MsgBoxImage}"
MsgBoxButton="{Binding confirmer.MsgBoxButton}"
Message="{Binding confirmer.Message}"
Caption="{Binding confirmer.Caption}"
Command="{Binding OkCommand}"
/>
Most of that is fairly self explanatory.
When ShowConfirmation is set to true, that control will show a messagebox.
If the user clicks OK or Yes then the OkCommand will be executed.
Hence if you need deletion confirmation then you show your messagebox and the actual deletion would be in that OkCommand.
If you want more complicated UI then rather than using a messagebox at all you could show a window from similar control.
Let's call this a DialogueController. This could be rather simpler since we can rely on our own UI and bind commands.
This control would have a dependency property for view Type. This being the type of a usercontrol which needs to be shown.
Another bool dependency property and change handler would control showing the type.
When the showdialog bool becomes true.
A method instantiates a window ( or it could be a popup if you prefer ) instantiates a usercontrol of the type specified in our other DP. Sets the window datacontext to our current datacontext.
You could use getwindow to find the parent window and set that to parent of our new (dialogue) window instance.
Thus sharing the parent window viewmodel as datacontext.
Call showdialog on the window. Or you could just call show.
You then have whatever UI you wrote in your usercontrol shown in a window.
It has access to your parent window viewmodel so it can reference any of your data you need.
And... It's Yes or OK button can bind to whatever command you defined in that parent window viewmodel.
You could also do things like select from a list and bind selectedFoo in your parent window viewmodel as well.
If you don't showmodal then when you change shared properties anything bound in your parent window can get those changes.
Like I said though.
I've not really come across much demand for that sort of thing.
There are also some elephants in the not-dialog room.
A pop up referencing it's parent viewmodel.
And
An expander similarly.
And
Just overlaying a panel on top of everything inside your parent window. This is how I have done editing for data in datagrids in a number of apps.
The key thing I found was the realisation:
You can "just" split your code into code before the dialog. Show the dialog. Then the "doing" aspect of any dialog-like-UI can go in a separate command.
OK, it's not exactly a road to damascus moment. But it simplifies things. I like simple. More likely to work.

Dependency Property design data

I have a UserControl that has an ObservableCollection dependency property with a property changed callback. The callback rebuilds the nodes in a TreeView control. This all works fine but I would like to be able to have design data. Unfortunately, neither the constructor, the callback nor a default value function call is called by the designer unless I embed my control in another. Is there a way loading default data in this scenario?
Below is the code behind for my control
public partial class ScheduleResourcesSummaryTreeView : UserControl
{
public ObservableCollection<PerformanceProjection> SelectedPerformances
{
get { return (ObservableCollection<PerformanceProjection>)GetValue(SelectedPerformancesProperty); }
set { SetValue(SelectedPerformancesProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedPerformancesProperty =
DependencyProperty.Register("SelectedPerformances",
typeof(ObservableCollection<PerformanceProjection>),
typeof(ScheduleResourcesSummaryTreeView),
new FrameworkPropertyMetadata(
new ObservableCollection<PerformanceProjection>(),
FrameworkPropertyMetadataOptions.AffectsRender, SelectedPerformancesChanged)
);
private static void SelectedPerformancesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ScheduleResourcesSummaryTreeView resourcesTree)) return;
resourcesTree.ResourcesTree.Items.Clear();
var sessions = e.NewValue as ObservableCollection<PerformanceProjection> ?? new ObservableCollection<PerformanceProjection>();
if (sessions.Count == 0) return;
var projections = sessions.SelectMany(x => x.ResourceBookingProjections);
TreeNode<ResourceBookingProjection> resourceBookingTreeRoot = new RvtSummaryTree(new ObservableCollection<ResourceBookingProjection>(projections), "");
foreach (var treeNode in resourceBookingTreeRoot.Children)
{
resourcesTree.ResourcesTree.Items.Add((TreeNode<ResourceBookingProjection>)treeNode);
}
}
private static ObservableCollection<PerformanceProjection> DefaultCollection()
{
var prop = DesignerProperties.IsInDesignModeProperty;
var designMode = (bool) DependencyPropertyDescriptor
.FromProperty(prop, typeof(FrameworkElement))
.Metadata.DefaultValue;
if (!designMode)
return new ObservableCollection<PerformanceProjection>();
var designdata = new PerformanceProjectionsViewModelDesignData();
return designdata.SelectedPerformances;
}
public ScheduleResourcesSummaryTreeView()
{
InitializeComponent();
}
}

WPF PropertyPath on Custom control

I have a problem, I create a control named Tile (like the Tile on Windows 10).
This tile simulate rotation 3D by using a projection class, like the projection class in Silverlight.
We have a base projection class like this :
abstract public class Projection
: FrameworkElement
{
static public readonly DependencyProperty RotationZProperty = DependencyProperty.Register(
nameof(RotationZ),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
static public readonly DependencyProperty RotationYProperty = DependencyProperty.Register(
nameof(RotationY),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
static public readonly DependencyProperty RotationXProperty = DependencyProperty.Register(
nameof(RotationX),
typeof(double),
typeof(Projection),
new UIPropertyMetadata(0.0, OnRotationChanged));
public double RotationZ
{
get { return this.GetValue<double>(RotationZProperty); }
set { SetValue(RotationZProperty, value); }
}
public double RotationY
{
get { return this.GetValue<double>(RotationYProperty); }
set { SetValue(RotationYProperty, value); }
}
public double RotationX
{
get { return this.GetValue<double>(RotationXProperty); }
set { SetValue(RotationXProperty, value); }
}
public FrameworkElement Child
{
get;
set;
}
static private void OnRotationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Projection pl)
{
pl.OnRotationChanged();
}
}
private void OnRotationChanged()
{
// Some code
}}
After that, we have the PlaneProjectionClass :
[ContentProperty("Child")]
sealed public class PlaneProjection
: Projection
{
}
The Tile class use a dependency property of type Projection :
public class Tile
{
static public readonly DependencyProperty PlaneProjectionProperty = DependencyProperty.Register(
nameof(Projection),
typeof(Projection),
typeof(Tile),
new UIPropertyMetadata());
public Projection Projection
{
get { return (Projection)GetValue(PlaneProjectionProperty); }
private set { SetValue(PlaneProjectionProperty, value); }
}
override public void OnApplyTemplate()
{
Projection = GetTemplateChild("PART_Projection") as Projection;
}
}
So for the XAML, we have this in ControlTemplate :
<controls:PlaneProjection x:Name="PART_PlaneProjection">
<Border>
<Grid>
<!-- Some design -->
</Grid>
</Border>
</controls:PlaneProjection>
Now I would like to animate the planeprojection.
So I create the storyboard and animate the projection with rotationX :
static public void CreateAnimation(Tile tile)
{
Storyboard.SetTarget(anim, tile);
Storyboard.SetTargetProperty(doubleAnim, new PropertyPath("(Tile.Projection).(PlaneProjection.RotationX"));
}
But at debug, I have this error : Cannot resolve all references of the property on the path of the property '(Tile.Projection).(PlaneProjection.RotationX)
I don't understand the mistake :( Any ideas on using PropertyPath on custom control ?
The Projection property in class Tile does not follow the naming conventions for dependency properties.
It should e.g. look like this:
public static readonly DependencyProperty PlaneProjectionProperty =
DependencyProperty.Register(nameof(PlaneProjection), typeof(Projection), typeof(Tile));
public Projection PlaneProjection
{
get { return (Projection)GetValue(PlaneProjectionProperty); }
private set { SetValue(PlaneProjectionProperty, value); }
}
The property path would simply be this:
Storyboard.SetTargetProperty(anim, new PropertyPath("PlaneProjection.RotationX"));
You wouldn't even need a Storyboard. Just call
tile.PlaneProjection.BeginAnimation(Projection.RotationXProperty, anim);
As a note, a private setter does not make the dependency property read-only. See Read-Only Dependency Properties for details.

Scenario with dependency properties-how to access each other

I have a two dependency properties(both List of strings) in a custom user control.The binding for one of these dependency properties can be changed several times for the life of the application. I need to do some action in the user control when the binding is changed, and I need to access all the dependency properties in the class for doing the action.
For example,
public class UC:UserControl
{
public List<string> AvailableItems
{
get { return (List<string>)this.GetValue(AvailableItemsProperty); }
set { this.SetValue(AvailableItemsProperty, value); }
}
public static readonly DependencyProperty AvailableItemsProperty = DependencyProperty.Register(
"AvailableItems", typeof(List<string>), typeof(ItemSelectionUserControl), new FrameworkPropertyMetadata(OnAvailableItemsChanged) { BindsTwoWayByDefault = true });
public List<string> SelectedItems
{
get { return (List<string>)this.GetValue(SelectedItemsProperty); }
set { this.SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems", typeof(List<string>), typeof(ItemSelectionUserControl), new FrameworkPropertyMetadata { BindsTwoWayByDefault = true });
public static void OnAvailableItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
//How to access SelectedItems here??
}
}
The trouble is the the callback when dependency property changed should be static, so how can I access the non static dependency property wrapper in the function?? Or is there any other way to do this??
Use the following:
public static void OnAvailableItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UC uc = sender as UC;
List<string> selectedItems = uc.SelectedItems;
}

WPF custom DependencyProperty notify changes

I have a class called MyComponent and it has a DependencyProperty caled BackgroundProperty.
public class MyComponent
{
public MyBackground Background
{
get { return (MyBackground)GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register("Background", typeof(MyBackground),
typeof(MyComponent), new FrameworkPropertyMetadata(default(MyBackground), new PropertyChangedCallback(OnPropertyChanged)));
}
MyBackground is a class that derives from DependencyObject and it has some DependencyProperties.
public class MyBackground : DependencyObject
{
public Color BaseColor
{
set { SetValue(BaseColorProperty, value); }
get { return (Color)GetValue(BaseColorProperty); }
}
public static readonly DependencyProperty BaseColorProperty =
DependencyProperty.Register("BaseColor", typeof(Color),
typeof(MyBackground ), new UIPropertyMetadata(Colors.White));
[...]
}
Now, what I want is when a property from MyBackground is changed, MyComponent to be notified that MyBackground has changed and the PropertyChangedCallback named OnPropertyChanged to be called.
Bear with me for a second because it appears that you are trying to go against the grain of WPF. Since it seems you are writing code related to display logic, the typical method for getting related DependencyObjects to interact with one another is through bindings.
If, for example, MyComponent is a control of some sort and it uses the Background property in its ControlTemplate, you would use a TemplateBinding that references the Background property and any important sub-properties.
Since 1) you probably already know that and 2) you either aren't using templates or don't have them available, you can set up a binding in code in order to react to changes in to the Background property. If you provide more detail about what your OnPropertyChanged method does I can provide some sample code.
One way to do what you describe would be to derive from Freezable instead of DependencyObject. When a property of a Freezable changes the PropertyChangedCallback for any DO referencing that Freezable will be invoked so the callback for the Background property of your MyComponent. In that case the e.OldValue and e.NewValue will be the same reference. Internally WPF has some flag on the event args that indicates that it is a subobject change.
This is what the framework does for things like brushes so that an element can be invalidated if say the Color property of a SolidColorBrush is changed. If an object will never be changed (or you want to make it thread safe) then one can freezing the object (i.e. making it immutable).
BTW I would probably avoid using Background as the name of the property. Most developers will assume that is of type Brush as that is what the framework uses for that named property on several of its elements (e.g. control, border).
Sounds like you want to use a DependencyPropertyDescriptor and AddValueChanged.
Here's an article on it: http://www.codeproject.com/Articles/34741/Change-Notification-for-Dependency-Properties.aspx
..and possibly a better implementation: http://agsmith.wordpress.com/2008/04/07/propertydescriptor-addvaluechanged-alternative/
Here's a small static class of extension methods I wrote for WPF -- it allows you to register an EventHandler or an Action callback for the changing of any DependencyProperty on any DependencyObject. No changes to the dependency object are necessary.
It also prevents recursion (i.e. if you change that same property during the callback, etc..)
It takes advantage of the DependencyPropertyDescriptor that #ScottBilas linked to.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
namespace BrainSlugs83.Writes.Too.Much.Code
{
public static class WpfExtensions
{
public static void OnPropertyChanged<T>(this T obj, DependencyProperty prop, Action<T> callback) where T : DependencyObject
{
if (callback != null)
{
obj.OnPropertyChanged(prop, new EventHandler((o, e) =>
{
callback((T)o);
}));
}
}
public static void OnPropertyChanged<T>(this T obj, DependencyProperty prop, EventHandler handler) where T : DependencyObject
{
var descriptor = DependencyPropertyDescriptor.FromProperty(prop, typeof(T));
descriptor.AddValueChanged(obj, new EventHandler((o, e) =>
{
if (handler != null)
{
if (o == null) { handler(o, e); }
else
{
lock (PreventRecursions)
{
if (IsRecursing(obj, prop)) { return; }
SetIsRecursing(obj, prop, true);
}
try
{
handler(o, e);
}
finally
{
SetIsRecursing(obj, prop, false);
}
}
}
}));
}
#region OnPropertyChanged Recursion Prevention
private static readonly Dictionary<object, List<DependencyProperty>> PreventRecursions = new Dictionary<object, List<DependencyProperty>>();
private static bool IsRecursing(object obj, DependencyProperty prop)
{
lock (PreventRecursions)
{
List<DependencyProperty> propList = null;
if (PreventRecursions.ContainsKey(obj))
{
propList = PreventRecursions[obj];
}
return propList == null ? false : propList.Contains(prop);
}
}
private static void SetIsRecursing(object obj, DependencyProperty prop, bool value)
{
lock (PreventRecursions)
{
List<DependencyProperty> propList = null;
if (PreventRecursions.ContainsKey(obj))
{
propList = PreventRecursions[obj];
}
if (propList == null)
{
if (!value) { return; }
propList = PreventRecursions[obj] = new List<DependencyProperty>();
}
if (value)
{
if (!propList.Contains(prop))
{
propList.Add(prop);
}
}
else
{
while (propList.Contains(prop))
{
propList.Remove(prop);
}
if (!propList.Any())
{
propList = PreventRecursions[obj] = null;
}
}
}
}
#endregion
public static bool IsInDesignMode(this DependencyObject obj)
{
try
{
return DesignerProperties.GetIsInDesignMode(obj);
}
catch { /* do nothing */ }
return false;
}
}
}

Resources