What is the order in which attached properties are applied to an object ? I guess I should ignore this, but here my scenario:
I've got an attached property to stick the VM to the View, and then, another attached property that depend on the first one. I'm trying to see what happen if the second is set up before the first, but I can't manage to get the error! ie the first ( the model ) is always set up before the second, whatever is the order in xaml. Who is driving the order of assigment? Can I change it?
Now I'm dealing with the late assigmement by subscribing the proeprty change event:
DependencyPropertyDescriptor dd = DependencyPropertyDescriptor.FromProperty(FrameworkElement.DataContextProperty,depo.GetType());
dd.AddValueChanged(depo, (s, a) =>
{
ChangeDatacontext(s as DependencyObject);
}
and for simulate the problem I setup manually a new datacontext to the object.
Thanks,
Felix
I can't directly answer this question, because I never rely on which property is set before the other, but you can manage things with a method that both attached properties use.
here is an example from my current code:
public static readonly DependencyProperty RuleVMProperty =
DependencyProperty.RegisterAttached("RuleVM", typeof(DocumentRuleViewModel), typeof(DocumentRuleViewModel), new UIPropertyMetadata(null, RuleVMChanged));
public static void RuleVMChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var el = GetRefid(sender);
var vm = args.NewValue as DocumentRuleViewModel;
if(vm==null)
return;
vm.SetDocumentFromRefid(sender, el);
}
public static readonly DependencyProperty RefidProperty =
DependencyProperty.RegisterAttached("Refid", typeof(XmlElement), typeof(DocumentRuleViewModel), new UIPropertyMetadata(RefidChanged));
public static void RefidChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var el = args.NewValue as XmlElement;
var vm = GetRuleVM(sender);
if (vm == null)
return;
vm.SetDocumentFromRefid(sender, el);
}
private void SetDocumentFromRefid(DependencyObject sender, XmlElement element)
{
... // this is where the actual logic sits
}
so essentially you have two changed handlers and whichever triggers last executes the logic because it sees if the other property is null.
Related
I want to add a method to the WPF Calendar control to enable it to select many dates at once, and raise the SelectedDatesChangedEvent only once at the end.
The WPF Calendar control allows you to add only one date at a time (ranges are not useful to me). However I might need to add some 1000 dates and I don't want the SelectedDatesChangedEvent handler called 1000 times because in my case it's an expensive operation.
The WPF DataGrid has a very nice feature that allows for this to be done:
public class MyDataGrid : DataGrid
{
public void ClearAndSelectMany(IEnumerable<DateTime> datesToBeSelected)
{
this.BeginUpdateSelectedItems();
...
foreach (DateTime date in datesToBeSelected)
this.SelectedDates.Add(date);
...
this.EndUpdateSelectedItems();
}
}
However the Calendar doesn't have anything like DataGrid's BeginUpdateSelectedItems(), so I'm trying to create a workaround by preventing base.OnSelectedDatesChanged() being called until it's all done:
public class MyCalendar : Calendar
{
protected override void OnSelectedDatesChanged(SelectionChangedEventArgs e)
{
if (false == this.temporaryDontReportSelectionChanged)
base.OnSelectedDatesChanged(e); // this is where I get an exception
}
public void ClearAndSelectMany(IEnumerable<DateTime> datesToBeSelected)
{
this.temporaryDontReportSelectionChanged = true;
...
foreach (DateTime date in datesToBeSelected)
SelectedDates.Add(date);
...
this.temporaryDontReportSelectionChanged = false;
OnSelectedDatesChanged(
new SelectionChangedEventArgs(
MyCalendar.SelectedDatesChangedEvent,
removedDates.ToList(),
addedDates.ToList()));
}
}
Now my problem is that I'm getting an exception when calling base.OnSelectedDatesChanged():
Unable to cast object of type
'System.EventHandler`1[System.Windows.Controls.SelectionChangedEventArgs]'
to type
'System.Windows.Controls.SelectionChangedEventHandler'.
I suppose I didn't properly create the SelectionChangedEventArgs object near the end, but I have no idea how to do it right. Any help will be appreciated.
Update: Motivated by Jamleck's question, I recreated the problem in a new project, and now have a bit more information to provide. Here's the MyCalendar class:
public class MyCalendar : System.Windows.Controls.Calendar
{
private bool temporaryDontReportSelectionChanged;
public MyCalendar()
{
// removing this line below makes my problem go away and it works ok
this.SelectedDatesChanged += MyCalendar_SelectedDatesChanged;
}
void MyCalendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
}
protected override void OnSelectedDatesChanged(SelectionChangedEventArgs e)
{
if (!temporaryDontReportSelectionChanged)
base.OnSelectedDatesChanged(e);
}
public void ClearAndSelectMany(IEnumerable<DateTime> datesToBeSelected)
{
this.temporaryDontReportSelectionChanged = true;
...
foreach (DateTime date in datesToBeSelected)
SelectedDates.Add(date);
...
this.temporaryDontReportSelectionChanged = false;
OnSelectedDatesChanged(
new SelectionChangedEventArgs(
MyCalendar.SelectedDatesChangedEvent,
removedDates.ToList(),
addedDates.ToList()));
}
}
So if I don't add an event handler to the SelectedDatesChanged event handler, everything works great, but if I do add it, then I get the InvalidCastException described above.
I have a parameterised constructor in My Application. I want to add controls dynamically to my silverlight Child Control Page. But it gives NullReferenceException.
I can't find out why it returns null.Can any help me with this situation?
public PDFExport(FrameworkElement graphTile1, FrameworkElement graphTile2,FrameworkElement graphTile3)
{
Button btnGraph1 = new Button();
string Name = graphTile1.Name;
btnGraph1.Content = Name;
btnGraph1.Width = Name.Length;
btnGraph1.Height = 25;
btnGraph1.Click += new RoutedEventHandler(btnGraph1_Click);
objStack.Children.Add(btnGraph1);
LayoutRoot.Children.Add(objStack); // Here am getting null Reference Exception
_graphTile1 = graphTile1;
_graphTile2 = graphTile2;
_graphTile3 = graphTile3;
}
Thanks.
I guess objStack is a stackpanel declared in your XAML?
Be aware that the UI component of your xaml are build by the call to InitializeComponent.
Thus objStack will not exist until you call InitializeCOmponent() in your constructor.
Also, you should know that the call to InitializeComponent is asynchronous, so you code should look like something like that:
private readonly FrameworkElement _graphTile1;
private readonly FrameworkElement _graphTile2;
private readonly FrameworkElement _graphTile3;
public PDFExport(FrameworkElement graphTile1, FrameworkElement graphTile2, FrameworkElement graphTile3)
{
_graphTile1 = graphTile1;
_graphTile2 = graphTile2;
_graphTile3 = graphTile3;
}
private void PDFExport_OnLoaded(object sender, RoutedEventArgs e)
{
Button btnGraph1 = new Button();
string Name = _graphTile1.Name;
btnGraph1.Content = Name;
btnGraph1.Width = Name.Length;
btnGraph1.Height = 25;
btnGraph1.Click += new RoutedEventHandler(btnGraph1_Click);
objStack.Children.Add(btnGraph1);
LayoutRoot.Children.Add(objStack);
}
Hope it helps.
As per my research i got that, why it raises an exception: Because there is no
InitializeComponent() in My Constructor and am not calling parent constructor.
That is the reason it raises Exception.
Just Add InitializeComponent() to the code, simple
i have created a custom user control which im using on my main xaml control:
<Controls:CustomControl Width="200" Height="20"
TotalCount="{Binding TotalRecordCount}" SuccessCount="{Binding ValidationCount}" ErrorCount="{Binding ValidationErrorCount}" Margin="0,5,0,0" HorizontalAlignment="Left">
</Controls:CustomControl>
I wanted to make the private variables of my custom usercontrol being ErrorCount,SuccessCount and total count(which are of type int32) Bindable so i can bind values to them. Right now when i try to bind it to my item source it gives the following error e exception message is "Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Int32'
Many thanks,
Michelle
You need to implement the Properties using DependencyProperty don't use private variables to hold these values. Here is an example:-
#region public int SuccessCount
public int SuccessCount
{
get { return (int)GetValue(SuccessCountProperty); }
set { SetValue(SuccessCountProperty, value); }
}
public static readonly DependencyProperty SuccessCountProperty =
DependencyProperty.Register(
"SuccessCount",
typeof(int),
typeof(CustomControl),
new PropertyMetadata(0, OnSuccessCountPropertyChanged));
private static void OnSuccessCountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomControl source = d as CustomControl;
int value = (int)e.NewValue;
// Do stuff when new value is assigned.
}
#endregion public int SuccessCount
In order for a property to be "Bindable", meaning that you can set it using DataBinding, that property needs to be a Dependency Property. For more info on Dependency Properties, please check this MSDN article.
hope this helps :)
I have a custom DepenencyProperty which determines a UserControl's visibility. It is usually bound to a boolean value, however I would like to set it False when the Escape key is hit.
The problem is, I don't want to overwrite the binding, I want to update the bindings source value. How can I do this in code behind?
For example, with this XAML
<local:MyControl IsVisibile="{Binding IsControlVisible}" />
I want to update the value of IsControlVisible to false, not MyControl.IsVisible
This should be possible via BindingExpressions, try something like this:
private void MyControl_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
var source = sender as FrameworkElement;
var expression = source.GetBindingExpression(UIElement.IsVisibleProperty);
(expression.DataItem as MyDataItem).IsControlVisible = false;
}
}
(If you do not reuse the UIElement.IsVisibleProperty you need to specify it via MyControl.IsVisibleProperty of course)
Here is a reflection-using method:
var source = sender as FrameworkElement;
var expression = source.GetBindingExpression(UIElement.IsVisibleProperty);
var dataType = expression.DataItem.GetType();
dataType.GetProperties().Single(x => x.Name == expression.ParentBinding.Path.Path)
.SetValue(expression.DataItem, false, null);
WPF 4? Use SetCurrentValue:
this.SetCurrentValue(IsControlVisibleProperty, false);
This won't overwrite the binding, but will instead push false to the binding source.
I have a GridView and want to serialize column widths across sessions. My idea of how to accomplish this is to attach a behavior to the GridViewColumns in such a way that each time the width of a column is changed the attached event handler is called and stores the new width. This already works well.
The only remaining problem:
How do I know in the event handler which GridViewColumn sent the event? I obviously need to know that in order to be able to store the width and later set the width on the correct column when restoring. Ideally I would like to use the name specified in XAML as column identifier.
Here is my code. XAML:
<GridView>
<GridViewColumn x:Name="GridColumn0"
HeaderTemplate="{StaticResource GridViewHeaderTemplate}" HeaderContainerStyle="{StaticResource GridViewHeaderStyle}"
Header="{x:Static strings:Strings.MainWindow_AppLog_Header_Severity}"
behaviors:GridViewBehaviors.PersistColumnWidth="True">
C# (please scroll down - question at bottom):
// Register the property used in XAML
public static readonly DependencyProperty PersistColumnWidthProperty =
DependencyProperty.RegisterAttached("PersistColumnWidth", typeof(bool), typeof(GridViewBehaviors),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnPersistColumnWidthChanged)));
// Provide read access to the value
public static bool GetPersistColumnWidth(DependencyObject d)
{
return (bool)d.GetValue(PersistColumnWidthProperty);
}
// Provide write access to the value (set from XAML)
public static void SetPersistColumnWidth(DependencyObject d, bool value)
{
d.SetValue(PersistColumnWidthProperty, value);
}
// This gets called once when the XAML is compiled to BAML
// Set the event handler
private static void OnPersistColumnWidthChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
GridViewColumn column = sender as GridViewColumn;
if (column == null)
return;
// Couple the UI event with a delegate
if ((bool)args.NewValue)
((INotifyPropertyChanged)column).PropertyChanged += new PropertyChangedEventHandler(PersistWidth);
else
((INotifyPropertyChanged)column).PropertyChanged -= new PropertyChangedEventHandler(PersistWidth);
}
// Deal with the events
static void PersistWidth(object sender, PropertyChangedEventArgs e)
{
GridViewColumn column = sender as GridViewColumn;
if (column == null)
return;
// We are only interested in changes of the "ActualWidth" property
if (e.PropertyName != "ActualWidth")
return;
// Ignore NaNs
if (column.ActualWidth == double.NaN)
return;
// Persist the width here
// PROBLEM:
// How to get a unique identifier for column, ideally its name set in XAML?
}
I don't think you will be able to access the x:Name, but you should be able to define your own column type that has a Name property that you can set.
So instead of adding GridViewColumns you add NamedColumn objects in your xaml.
Define a type that derives from GridViewColumn:
public class NamedColumn : GridViewColumn
{
public string ColumnName {get; set;}
}
And use it in your xaml:
<GridView>
<NamedColumn ColumnName="GridColumn0" .... blablalba more stuff here />
...
NoW you should be able to cast the sender to a NamedColumn and access its name property.
can you use column.Header.ToString()? see here for why name from XAML isn't possible: How to get x:Name value runtime
Thanks for your answers, Robert, Thomas and Rune. I like Runes answer, but I found something even easier for my situation here:
I changed the type of the attached property from bool to string and simply store the name of the column there. The relevant changes are below.
XAML:
<GridViewColumn
behaviors:GridViewBehaviors.PersistColumnWidth="MainWindow_AppLog_Column0">
C#:
public static readonly DependencyProperty PersistColumnWidthProperty =
DependencyProperty.RegisterAttached("PersistColumnWidth", typeof(string),
typeof(GridViewBehaviors), new FrameworkPropertyMetadata(string.Empty,
new PropertyChangedCallback(OnPersistColumnWidthChanged)));
static void PersistWidth(object sender, PropertyChangedEventArgs e)
{
// This now yields "MainWindow_AppLog_Column0"
string columnID = GetPersistColumnWidth(column);
I'm not sure it would work, but you can try something like that:
INameScope nameScope = NameScope.GetNameScope(column);
string name = nameScope
.Where(kvp => kvp.Value == column)
.Select(kvp => kvp.Key.ToString())
.FirstOrDefault();