Delegating UI actions to ViewModel for execution - c#

I have an ScrollViewer which holds an ItemsSource. The items are bound to the ViewModel and are sorted in ascending order. The users are allowed to add items to this list, however, the list needs to be scrolled to bottom because of the sorting order. From what I found, ScrollViewer doesn't have a "lock to bottom" feature, but has a ScrollToEnd method which does what I'm looking for.
The problem though, is that the items are added in the ViewModel, and the ViewModel obviously doesn't have access to the View to call the ScrollToEnd method on the ScrollViewer. To get around this, I declared an action delegate in the ViewModel like this:
public Action ScrollAction { get; set; }
And set it in the View upon creation of the ViewModel:
viewModel.ScrollAction = () => scrollViewer.ScrollToEnd();
The delegate is executed in the ViewModel once an item is added to the list. Even though this works, it feels a little hacky to me, since this kind of breaks the isolation of the ViewModel from the View. Is there a better way to achieve this?

I would also vote for an AttachedProperty to your scroll viewer.
I created following attached property to bind scroll to end with a boolean variable.
public static class ScrollViewerBehavior
{
public static readonly DependencyProperty ScrollToRightEndProperty =
DependencyProperty.RegisterAttached("ScrollToRightEnd", typeof (bool), typeof (ScrollViewerBehavior),
new PropertyMetadata(false, OnScrollToRightEndPropertyChanged));
public static bool GetScrollToRightEnd(DependencyObject obj)
{
return (bool) obj.GetValue(ScrollToRightEndProperty);
}
public static void SetScrollToRightEnd(DependencyObject obj, bool value)
{
obj.SetValue(ScrollToRightEndProperty, value);
}
private static void OnScrollToRightEndPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var sv = sender as ScrollViewer;
if (sv == null) return;
if ((bool) e.NewValue)
{
sv.ScrollToRightEnd();
}
else
{
sv.ScrollToLeftEnd();
}
}
}
You can use this attached property in your XAML...
<ScrollViewer ... local:ScrollViewerBehavior.ScrollToRightEnd="{Binding IsScrolledToEnd}" ... />
Alternatively if you want to save the action delegate as in your question, you could do the following in the OnScrollToRightEndPropertyChanged method above.
.....
var viewModel = sv.DataContext as YourViewModel;
if (viewModel != null)
{
viewModel.ScrollAction = () => sv.ScrollToRightEnd();
}
.....

Related

How to fire Property Changed Event when it's not actually changing

so I have a model which contains 2 variables, a List and a DateTime. In my UserControl I have a DependencyProperty and I also defined a PropertyChangedCallback.
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("My", typeof(List<MyContainer>), typeof(UC), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnMyProperty)));
public List<MyContainer> My
{
get
{
return GetValue(MyProperty) as List<MyContainer>;
}
set
{
SetValue(MyProperty, value);
}
}
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
//do stuff
}
On my form there is a button, which do the changes on the other model variable (on the DateTime).
private void Date_Click(object sender, RoutedEventArgs e)
{
MyModel model = DataContext as MyModel;
if (model != null)
{
model.Date = model.Date.AddDays(1);
}
}
And finally here is my model.
public class MyModel : INotifyPropertyChanged
{
private List<MyContainer> _My;
private DateTime _Date;
public MyModel()
{
_Date = DateTime.Now.Date;
_My = new List<MyContainer>();
}
public List<MyContainer> My
{
get
{
return _My;
}
set
{
_My = value;
OnPropertyChanged("My");
}
}
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML declaration is the following.
<local:UC My="{Binding My}" />
So my problem is the after I hit the run, it fires the OnMyProperty once, after that if I hit the button, it changes the DateTime property well, but the OnMyProperty callback doesn't firing again. However I noticed that if I modify my model like this
public DateTime Date
{
get
{
return _Date;
}
set
{
_Date = value;
_My = new List<MyContainer>(_My); //added
OnPropertyChanged("Date");
OnPropertyChanged("My");
}
}
now it fires it every time when I hit the button. How can I trigger the second behaviour without that modification?
After setting the value of a DependencyProperty it first checks if the new value is different to the old one. Only in this case the PropertyChangedCallback method you registered with that DependencyProperty is called. So the name PropertyChanged makes sense.
In your (not modified) case you not even try to change My (only Date). So there is no reason to raise the callback function.
The answer is that you almost certainly do not need to do this. When you ask a question about how to make the framework do something it really does not want to do, always say why you think you need to do that. It's very likely that there's a much easier answer that everybody else is already using.
The only thing you have bound to the control is My. Therefore, if My hasn't changed, then the state of the control should not change. If you want the state of the control to change when Date changes, bind Date to some property of the control. The only way the control should ever get information from any viewmodel is through binding one of its dependency properties to a property of the viewmodel.
The control should not ever know or care who or what is providing values for its properties. It should be able to do its job knowing only the property values it has been given.
If the contents of My have changed -- you added an item or removed one -- of course the control has no way of knowing that, because you refused to tell it. You're just telling it there's a new list. It checks, sees it's still got the same old list, and ignores you. The My property of your viewmodel should be an ObservableCollection, because that will notify the control when you add or remove items in the collection.
The items themselves, your MyContainer class, must implement INofityPropertyChanged as well, if you want to be able to change their properties while they are displayed in the UI.
The dependency property My on your control must not be of type List<T>. It should probably be type object, just like ItemsControl.ItemsSource. Then your control template can display it in an ItemsControl which knows what to do with it. If an ObservableCollection is bound to it as I suggested above, the ItemsControl will update automatically. In OnMyProperty, your control class can check to see if it's an observable collection as well:
private static void OnMyProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UC control = d as UC;
if (e.NewValue is INotifyCollectionChanged)
{
(e.NewValue as INotifyCollectionChanged).CollectionChanged +=
(s, ecc) => {
// Do stuff with UC and ecc.NewItems, ecc.OldItems, etc.
};
}
}

Dynamic Data Display graph of variable number of rectangles

I'm trying to plot a user's input data that will eventually become a series of rectangles (different sizes and positions, not overlapping) in a graph. All examples I've read either only plot lines with a variable number of points or hard-code in the shapes in the XAML. But I don't know how many rectangles the data will need. My ideal case would be to follow MVVM and simply bind from the XAML to an ObservableCollection I can modify, but most examples I see seem to use the code-behind instead, accessing the ChartPlotter directly. Here's what I've got with some simple rectangles drawn and one modified, which works:
VisualizationWindow.xaml
<Window x:Class="View.VisualizationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
Title="VisualizationWindow" MinHeight="300" MinWidth="500" Height="300" Width="500">
<Grid>
<d3:ChartPlotter Name="Chart">
<d3:RectangleHighlight Name="Rect1" Bounds="-1,-1.5,.5,2" StrokeThickness="3" Fill="Blue" ToolTip="Blue!"></d3:RectangleHighlight>
<d3:RectangleHighlight Name="Rect2" Bounds="1,1.5,2,.5" StrokeThickness="1" Fill="Red" ToolTip="Red!"></d3:RectangleHighlight>
</d3:ChartPlotter>
</Grid>
</Window>
VisualizationWindow.xaml.cs
public partial class VisualizationWindow : Window
{
public VisualizationWindow(ListViewModel vm)
{
InitializeComponent();
this.DataContext = vm;
Chart.Viewport.Visible = new Rect(-1, -1, .5, .5);
Rect1.Bounds = new Rect(0,0,.3,.3);
}
}
Documentation on Dynamic Data Display is almost nonexistent. I'd love to know if other libraries can do this more easily if D3 can't do it elegantly.
Adding ItemsSource handling to existing classes.
It seems like they've done everything to make ChartPlotter only accept IPlotterElements. There's no ItemsSource property, so Children will always return the actual elements. RectangleHighlight's Bounds property is not bindable and the class is sealed, barring any methods to override the property.
We can derive from the class to "inject" ItemsSource handling. It won't work like the real deal, as there's no non-hackish way to have the Children property reflect the data-binding. But, we can still assign ItemsSource this way.
We'll need a few things. We'll need the actual ItemsSource property. A way to react to it being set. And if we want binding to dataObjects, a way to handle DataTemplates. I didn't dig into the existing source code yet. However, I did come up with a way to Handle DataTemplates without DataTemplateSelector. But it also won't work with DataTemplateSelector unless you modify my examples.
This answer assumes you know how binding works, so we'll skip to the initial classes without much hand-holding.
Xaml first:
<local:DynamicLineChartPlotter Name="Chart" ItemsSource="{Binding DataCollection}">
<local:DynamicLineChartPlotter .Resources>
<DataTemplate DataType{x:Type local:RectangleHighlightDataObject}>
<d3:RectangleHighlight
Bounds="{Binding Bounds}"
StrokeThickness="{Binding StrokeThickness}"
Fill="{Binding Fill}"
ToolTip="{Binding ToolTip}"
/>
</DataTemplate>
</local:DynamicLineChartPlotter .Resources>
</local:DynamicLineChartPlotter >
Classes:
public class RectangleHighlightDataObject
{
public Rect Bounds { get; set; }
public double StrokeThickness { get; set; }
public Brush Fill { get; set; }
public String ToolTip { get; set; }
}
public class VisualizationWindow
{
public VisualizationWindow()
{
DataCollection.Add(new RectangleHighlightDataObject()
{
Bounds = new Rect(-1,-1.5,.5,2),
StrokeThickness = 3,
Fill = Brushes.Blue,
ToolTip = "Blue!"
});
DataCollection.Add(new RectangleHighlightDataObject()
{
Bounds = new Rect(1,1.5,2,.5),
StrokeThickness = 1,
Fill = Brushes.Red,
ToolTip = "Red!"
});
}
public ObservableCollection<RectangleHighlightDataObject> DataCollection =
new ObservableCollection<RectangleHighlightDataObject>();
}
You'll have to use a derived class from ChartPlotter than implements an ItemsSource.
An example gleaned from a discussion on how to implement dynamic D3 types. I modified to use DataTemplates instead of actual object elements, this is to support databinding par the OP.
public class DynamicLineChartPlotter : Microsoft.Research.DynamicDataDisplay.ChartPlotter
{
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource",
typeof(IEnumerable),
typeof(DynamicLineChartPlotter),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Bindable(true)]
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
if (value == null)
ClearValue(ItemsSourceProperty);
else
SetValue(ItemsSourceProperty, value);
}
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DynamicLineChartPlotter control = (DynamicLineChartPlotter)d;
IEnumerable oldValue = (IEnumerable)e.OldValue;
IEnumerable newValue = (IEnumerable)e.NewValue;
if (e.OldValue != null)
{
control.ClearItems();
}
if (e.NewValue != null)
{
control.BindItems((IEnumerable)e.NewValue);
}
}
private void ClearItems()
{
Children.Clear();
}
private void BindItems(IEnumerable items)
{
foreach (var item in items)
{
var template = GetTemplate(item);
if (template == null) continue;
FrameworkElement obj = template.LoadContent() as FrameworkElement;
obj.DataContext = item;
Children.Add((IPlotterElement)obj);
}
}
private DataTemplate GetTemplate(object item)
{
foreach (var key in this.Resources.Keys)
{
if (((DataTemplateKey)key).DataType as Type == item.GetType())
{
return (DataTemplate)this.Resources[key];
}
}
return null;
}
}
Now this is where you hit a brick wall.
RectangleHighlight Bounds property cannot be data-bound. You also can't derive from them to get around this problem.
We could hack our way around by pulling out the data template and generating a static RectangleHighlight, but if the data values change, we're sol.
So, how to fix this?
Well, we could use attached properties!
Using Attached Properties
We'll create a static class that will handle the attached property. It responds to the OnPropertyChanged to manually create and set the real property. Now, this will only work one way. If you happen to change the property, it won't update the attached property. However, this shouldn't be a problem since we should only ever update our data object.
Add this class
public class BindableRectangleBounds : DependencyObject
{
public static DependencyProperty BoundsProperty = DependencyProperty.RegisterAttached("Bounds", typeof(Rect), typeof(BindableRectangleBounds), new PropertyMetadata(new Rect(), OnBoundsChanged));
public static void SetBounds(DependencyObject dp, Rect value)
{
dp.SetValue(BoundsProperty, value);
}
public static void GetBounds(DependencyObject dp)
{
dp.GetValue(BoundsProperty);
}
public static void OnBoundsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
{
var property = dp.GetType().GetProperty("Bounds");
if (property != null)
{
property.SetValue(dp, args.NewValue, null);
}
}
}
Then change the XAML line from
Bounds="{Binding Bounds}"
to
local:BindableRectangleBounds.Bounds="{Binding Bounds}"
Responding to collection changed.
So far so good. But the OP noticed that if he made changes to the collection he assigned ItemsSource to, nothing changes in the control. Well that's because we only add children when ItemsSource is assigned to. Now, barring knowing exactly how ItemsControl implements ItemsSource. I know we can get around this by registering to the ObservableCollection's events when the collection changes. I put a simple method of rebinding the controls whenever the collection changes. This will only work if ItemsSource is assigned by an ObservableCollection though. But I would think this would have the same problem with ItemsControl not having an ObservableCollection.
We're still not reassigning the dataContext however. So don't expect altering Children would correctly rebind. However if you did alter children directly, instead of the ItemsSource in an ItemsControl, you'd lose the binding. So I may very well be on track. The only quirk we lose is that ItemsControl returns the ItemsSource when you refer to Children after setting ItemsSource. I haven't worked around this yet. The only thing I can think of offhand is to hide the children property, but that's not a nice thing to do, and won't work if you refer to the control as a ChartPlotter.
Add the following
public DynamicLineChartPlotter()
{
_HandleCollectionChanged = new NotifyCollectionChangedEventHandler(collection_CollectionChanged);
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DynamicLineChartPlotter control = (DynamicLineChartPlotter)d;
IEnumerable oldValue = (IEnumerable)e.OldValue;
IEnumerable newValue = (IEnumerable)e.NewValue;
INotifyCollectionChanged collection = e.NewValue as INotifyCollectionChanged;
INotifyCollectionChanged oldCollection = e.OldValue as INotifyCollectionChanged;
if (e.OldValue != null)
{
control.ClearItems();
}
if (e.NewValue != null)
{
control.BindItems((IEnumerable)e.NewValue);
}
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control._HandleCollectionChanged;
control._Collection = null;
}
if (collection != null)
{
collection.CollectionChanged += control._HandleCollectionChanged;
control._Collection = newValue;
}
}
void collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ClearItems();
BindItems(_Collection);
}
NotifyCollectionChangedEventHandler _HandleCollectionChanged;
IEnumerable _Collection;

Dependency Property Update Mechanism

I have a custom control which is having a dependency property defined in it and my control implements INotifyPropertyChanged interface.
Dependency Property is Collection of Custom Objects.
Scenario 1
DP is of type List, whatever change I make in the list, nothing updated in MainUI, because I believe WPF does not understand adding and removing objects in list. it understands completely new references so to achieve this, whenever I want to update my list on control I use
MyProperty=new List();
In this approach, my DP callback fires everytime but eventArgs.NewValue always remains zero(it updated the list on UI correctly) but because I need to write some logic in property changed callback based on e.NewItems.Count, in this case that didn't work. Please tell me why e.NewItems does not work.
Scenario 2
DP is of type ObservableCollection, so as my collection property in view model against which I am binding my DP. in this case my property change callbacks does not fire at all, because I never use "new" keyword again after initialzing the property first time. UI updates but property change still not fires. So my logic in property change call back does not gets executed.
How should I make any of them or both of them working.
I would use the ObservableCollection approach, and subscribe to it's CollectionChanged event.
That way you will get notified whenever the collection has been changed.
But the other approach should work as well. When you set the regular list to a new instance, the PropertyChangedCallback will be fired for the dependency property, and by examining the DependencyPropertyChangedEventArgs object you can get the new value.
XAML:
<StackPanel>
<Button Content="Add to observablecollection" Click="click1" />
<Button Content="Set list to new instance" Click="click2" />
</StackPanel>
Code-behind:
public partial class Window1 : Window
{
public ObservableCollection<string> Strings { get; set; }
public List<string> StringsList
{
get { return (List<string>)GetValue(StringsListProperty); }
set { SetValue(StringsListProperty, value); }
}
public static readonly DependencyProperty StringsListProperty =
DependencyProperty.Register("StringsList", typeof(List<string>), typeof(Window), new PropertyMetadata(null, StringsListPropertyChanged));
public Window1()
{
InitializeComponent();
Strings = new ObservableCollection<string>();
Strings.CollectionChanged += strings_CollectionChanged;
StringsList = new List<string> { "Test1", "Test2", "Test3", "Test4" };
}
void strings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Fires everytime the observablecollection has an item added/removed etc.
MessageBox.Show(string.Format("ObservableCollection has changed! Count is now {0}", this.Strings.Count.ToString()));
if (this.Strings.Count == 10)
Console.WriteLine("Collection contains 10 strings!!");
}
private static void StringsListPropertyChanged(DependencyObject e, DependencyPropertyChangedEventArgs args)
{
var newCount = ((List<string>)args.NewValue).Count.ToString();
MessageBox.Show(string.Format("Dependency property has changed! Count is now {0}", newCount));
}
private void click1(object sender, RoutedEventArgs e)
{
this.Strings.Add("Test1");
}
private void click2(object sender, RoutedEventArgs e)
{
this.StringsList = new List<string> { "Newitem1", "Newitem2" };
}
}
ObservableCollection inherits from both INotifyPropertyChanged and INotifyCollectionChanged. I think if you want to know when the collection changed you should use this interface:
INotifyCollectionChanged

AvalonDock - Bind MenuItem to State of DockableContent

I'm using AvalonDock to layout my application.
I want to create a "View" MenuItem with a checkable MenuItem for each of my DockableContents that will show/hide each item.
I'm not finding an example of anyone doing this, and it appears to me the State property is readonly, making it not possible to create a 2-way binding to the MenuItem. It also looks like you have to call methods to change the State.
Anyone have a clever way to do this with bindings? Or is there a simple way to do it I'm missing.
One possible solution is to use an attached property. The attached property would call the necessary methods to change the state. You could then bind to that.
public static class ContentAttach
{
public static readonly DependencyProperty StateProperty = DependencyProperty.RegisterAttached(
"State", typeof(DockableContentState), typeof(ContentAttach), new PropertyMetadata(StateChanged));
public static void SetState(DockableContent element, DockableContentState value)
{
element.SetValue(StateProperty, value);
}
public static DockableContentState GetState(DockableContent element)
{
return (DockableContentState)element.GetValue(StateProperty);
}
private static void StateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = (DockableContent)d;
var state = (DockableContentState)e.NewValue;
switch (state)
{
// Call methods in here to change State.
}
}
}

Wrapped WPF Control

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.

Categories

Resources