I have the following DependencyProperty in a custom control:
public bool HasConnection
{
get { return (bool)GetValue(HasConnectionProperty); }
set { SetValue(HasConnectionProperty, value); }
}
public static readonly DependencyProperty HasConnectionProperty =
DependencyProperty.Register(
"HasConnection",
typeof(bool),
typeof(NetworkNode),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(HasConnectionChangedCallBack)));
private static void HasConnectionChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
NetworkNode nn = (NetworkNode)d;
Ellipse el = nn.GetTemplateChild("PART_inner") as Ellipse;
if (el.PART_inner.Visibility == ...) <-- exception el is null
//..code..
}
Runs fine, but if I change the property in Properties panel of my custom control, at run time throws an exception: Object reference not set to an instance of an object.
Edit1:
Forgot to add one line of code in the post Ellipse el = nn.GetTemplateChild("PART_inner") as Ellipse;
Edit2:
Creating a BooleanToVisibilityConverter and using Binding in Generic.xaml works, but the HasConnectionChangedCallBack method is now empty/useless.
Visibility="{Binding HasConnection, Converter={StaticResource BooleanToVisibiltyConverter}, RelativeSource={RelativeSource TemplatedParent}}"
Edit3:
Found a posible fix. The property callback method is called first then the OnApplyTemplate() method, so no more exceptions thrown or error in xaml.
In OnApplyTemplate() I add
if (this.HasConnection)
PART_inner.Visibility = System.Windows.Visibility.Visible;
else
PART_inner.Visibility = System.Windows.Visibility.Hidden;
Do this
private static void HasConnectionChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
return;
NetworkNode nn = (NetworkNode)d;
if (nn == null || nn.Part_inner == null )
return;
if (nn.PART_inner.Visibility == ...) <-- exception
//..code..
}
The reason for the exception is that when the property is set through the XAML parser, the content of the UserControl has not been instantiated.
The XAML parser works its way from top to bottom through the XAML. A UserControl is just a shortcut for the XAML that defines it, so, at the time when the XAML parser sets HasConnection=True on the outer control, its content has not yet been instantiated, so PART_Inner doesn't yet exist.
The solution is to define the relationship between HasConnection and whatever depends on it in the UserControl in a way that keeps the instantiation sequence in mind. For example, if PART_Inner is a UserControl, you could search for its parent of type NetworkNode in its Loaded event, so that HasConnection can be evaluated then. This is probably the solution which requires the least changes to your existing code. Leave the change handler as it is now, including the safety code, and add logic to the contained control which reads the start value from its ancestor.
Other options would be to not use a DependencyPropertyChanged callback at all, but create a Binding on the Visibility property using a RelativeSource typed FindAncestor and a BooleanToVisibilityConverter. Still another idea would be to use a Trigger.
Related
Can anyone explain how XAML data binding expressions are evaluated? I have a control with a registered property, VisualState.
public CardStates VisualState
{
get
{
return (CardStates)this.GetValue(VisualStateProperty);
}
set
{
this.SetValue(VisualStateProperty, value);
}
}
public static readonly DependencyProperty VisualStateProperty = DependencyProperty.RegisterAttached("VisualStateProperty", typeof(CardStates), typeof(StateManager), new PropertyMetadata(null, (s, e) => { }));
In the xaml I attempt to bind a value to this property. State exists parent's DataContext object.
<local:CardControl VisualState="{Binding State.Value}" />
The generated code in XamlTypeInfo.g.cs looks like this
private void set_4_CardControl_VisualState(object instance, object Value)
{
var that = (global::MeetEric.UI.Controls.CardControl)instance;
that.VisualState = (global::MeetEric.ViewModels.CardStates)Value;
}
This code throws an InvalidCastException because the value of Value is a Windows.UI.Xaml.Data.Binding object.
Am I missing something obvious to enable working with data bindings? Do I need some form of converter?
Try to change typeof(StateManager) to typeof(CardControl). (at DependencyProperty.RegisterAttached)
This argument requires the owner of the DependencyProperty.
This error occurs when you try to using Binding with a regular property. In order for the binding to resolve to the right type, you need to be binding to a DependencyProperty.
As others have mentioned, your syntax is a bit off which is probably causing the problem here.
This error is certainly very cryptic. I just hit it in my own project, but as soon as I changed my property to a dependency one, it worked perfect.
I have a control in an assembly that I can't change that is very similar to the .NET DateTimePicker. I want to hide the time picker portion of that control when a certain condition is met (Property value on my ViewModel). The control looks like this:
[TemplatePart(Name = "PART_DatePicker", Type = typeof (DatePicker))]
[TemplatePart(Name = "PART_TimePicker", Type = typeof (TimePicker))]
public class MyDateTimePicker : Control {/*...*/}
This answer shows a nice way to always hide a PART of a control, but I want to do it dynamically:
How to hide a part of a WPF control
I imagine there are a few ways to do this. What I want is something minimal (like in the linked question's answer) as well as something that doesn't violate MVVM. System.Interactivity behaviors and triggers are fair game.
Create a new control extending the previous one
public sealed class MySuperiorDateTimePicker : MyDateTimePicker
{
//....
Add a DependencyProperty that you can bind to your ViewModel's state
public static readonly DependencyProperty HideItProperty =
DependencyProperty.Register(
"HideIt",
typeof(bool),
typeof(MySuperiorDateTimePicker ),
new UIPropertyMetadata(false, HideItPropertyChanged));
//snip property impl
Wait for the property to change, then hide your UI
private static void HideItPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
(d as MySuperiorDateTimePicker).OnHideItChanged((bool)e.OldValue,
(bool)e.NewValue);
}
private void OnHideItChanged(bool oldValue, bool newValue)
{
if(BusyTemplate == null)
return;
FindTimePicker().Visibility = newValue ? Visibility.Visible :
Visibility.Collapsed;
}
private UIElement FindTimePicker()
{
//snip null checks
return GetTemplateChild("PART_TimePicker") as UIElement;
}
Be careful with FindTimePicker as your DP might change before the control is loaded, and GetTemplateChild will return null. The usual thing to do is, in OnHideItChanged, if GetTemplateChild returns null use Dispatcher.BeginInvoke to re-run the event handler later on (ApplicationIdle or earlier).
When you find yourself saying "How can I do UI work using MVVM" stop and rethink your true goals. MVVM != no codebehind, no custom controls, etc.
One solution would be to hide it with the help of a DataTrigger defined in the datatemplate, so that when a certain value in the datacontext of the control is set to true/false then you will hide/show the part.
A quick search and i found some links that you might find useful:
http://zamjad.wordpress.com/2010/06/22/conditionally-hide-controls-from-data-template/
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/ae2dbfb7-5dd6-4352-bfa1-53634289329d/
The solution that worked for me was to edit the style of the control. Using Blend, I edited a copy of the style of the DateTimePicker, and added a binding to Visibility of the TimePicker that looks at my VM and converts the value of the enumeration.
I have a requirement to focus on a specific textbox when a new view is loaded.
The solution was to add this line of code to the OnLoaded event for the view:
Dispatcher.BeginInvoke(() => { NameTextBox.Focus(); });
So this worked for one view, but not another. I spent some time debugging the problem and realized that the new view I was working on had a BusyIndicator that takes focus away from all controls since the BusyIndicator being set to true and false was occuring after the OnLoaded event.
So the solution is to call focus to the NameTextBox after my BusyIndicator has been set to false. My idea was to create a reusable BusyIndicator control that handles this extra work. However, I am having trouble doing this in MVVM.
I started by making a simple extension of the toolkit:BusyIndicator:
public class EnhancedBusyIndicator : BusyIndicator
{
public UserControl ControlToFocusOn { get; set; }
private bool _remoteFocusIsEnabled = false;
public bool RemoteFocusIsEnabled
{
get
{
return _remoteFocusIsEnabled;
}
set
{
if (value == true)
EnableRemoteFocus();
}
}
private void EnableRemoteFocus()
{
if (ControlToFocusOn.IsNotNull())
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
else
throw new InvalidOperationException("ControlToFocusOn has not been set.");
}
I added the control to my XAML file with no problem:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
RemoteFocusIsEnabled="{Binding IsRemoteFocusEnabled}"
IsBusy="{Binding IsDetailsBusyIndicatorActive}"
...
>
...
<my:myTextBox (this extends TextBox)
x:Name="NameTextBox"
...
/>
...
</my:EnhancedBusyIndicator>
So the idea is when IsRemoteFocusEnabled is set to true in my ViewModel (which I do after I've set IsBusy to false in the ViewModel), focus will be set to NameTextBox. And if it works, others could use the EnhancedBusyIndicator and just bind to a different control and enable the focus appropriately in their own ViewModels, assuming their views have an intial BusyIndicator active.
However, I get this exception when the view is loaded:
Set property 'foo.Controls.EnhancedBusyIndicator.ControlToFocusOn' threw an exception. [Line: 45 Position: 26]
Will this solution I am attempting work? If so, what is wrong with what I have thus far (cannot set the ControlToFocusOn property)?
Update 1
I installed Visual Studio 10 Tools for Silverlight 5 and got a better error message when navigating to the new view. Now I gete this error message:
"System.ArgumentException: Object of type System.Windows.Data.Binding cannot be converted to type System.Windows.Controls.UserControl"
Also, I think I need to change the DataContext for this control. In the code-behind constructor, DataContext is set to my ViewModel. I tried adding a DataContext property to the EnhancedBusyIndicator, but that did not work:
<my:EnhancedBusyIndicator
DataContext="{Binding RelativeSource={RelativeSource Self}}"
ControlToFocusOn="{Binding ElementName=NameTextBox}"
RemoteFocusIsEnabled="{Binding IsRemoteFocusEnabled}"
IsBusy="{Binding IsDetailsBusyIndicatorActive}"
...
>
Update 2
I need to change UserControl to Control since I will be wanting to set focus to TextBox objects (which implement Control). However, this does not solve the issue.
#Matt, not sure
DataContext="{Binding RelativeSource={RelativeSource Self}}"
will work in Silverlight 5, have you tried binding it as a static resource?
Without a BusyIndicator present in the view, the common solution to solve the focus problem is to add the code
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
to the Loaded event of the view. This actually works even with the BusyIndicator present; however, the BusyIndicator immediately takes focus away from the rest of the Silverlight controls. The solution is to invoke the Focus() method of the control after the BusyIndicator is not busy.
I was able to solve it by making a control like this:
public class EnhancedBusyIndicator : BusyIndicator
{
public EnhancedBusyIndicator()
{
Loaded += new RoutedEventHandler(EnhancedBusyIndicator_Loaded);
}
void EnhancedBusyIndicator_Loaded(object sender, RoutedEventArgs e)
{
AllowedToFocus = true;
}
private readonly DependencyProperty AllowedToFocusProperty = DependencyProperty.Register("AllowedToFocus", typeof(bool), typeof(EnhancedBusyIndicator), new PropertyMetadata(true));
public bool AllowedToFocus
{
get { return (bool)GetValue(AllowedToFocusProperty); }
set { SetValue(AllowedToFocusProperty, value); }
}
public readonly DependencyProperty ControlToFocusOnProperty = DependencyProperty.Register("ControlToFocusOn", typeof(Control), typeof(EnhancedBusyIndicator), null);
public Control ControlToFocusOn
{
get { return (Control)GetValue(ControlToFocusOnProperty); }
set { SetValue(ControlToFocusOnProperty, value); }
}
protected override void OnIsBusyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsBusyChanged(e);
if (AllowedToFocus && !IsBusy)
{
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
AllowedToFocus = false;
}
}
}
To use it, replace the BusyIndicator tags in your xaml with the new EnhancedBusyIndicator and add the appropriate namespace.
Add a new property, ControlToFocusOn inside the element, and bind it to an existing element in the view that you want focus to be on after the EnhancedBusyIndicator disappears:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
...
>
...
</my:EnhancedBusyIndicator>
In this case, I focused to a textbox called NameTextBox.
That's it. This control will get focus every time we navigate to the page. While we are on the page, if the EnhancedBusyIndicator becomes busy and not busy agiain, focus will not go to the control; this only happens on initial load.
If you want to allow the EnhancedBusyIndicator to focus to the ControlToFocusOn another time, add another property, AllowedToFocus:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
AllowedToFocus="{Binding IsAllowedToFocus}"
...
>
...
</my:EnhancedBusyIndicator>
When AllowedToFocus is set to true, the next time EnhancedBusyIndicator switches from busy to not busy, focus will go to ControlToFocusOn.
AllowedToFocus can also be set to false when loading the view, to prevent focus from going to a control. If you bind AllowedToFocus to a ViewModel property, you may need to change the BindingMode. By default, it is OneTime.
I am trying to set the initial focus to a control in a Silverlight form. I am trying to use attached properties so the focus can be specified in the XAML file. I suspect that the focus is being set before the control is ready to accept focus. Can anyone verify this or suggest how this technique might be made to work?
Here is my XAML code for the TextBox
<TextBox x:Name="SearchCriteria" MinWidth="200" Margin ="2,2,6,2" local:AttachedProperties.InitialFocus="True"></TextBox>
The property is defined in AttachedProperties.cs:
public static DependencyProperty InitialFocusProperty =
DependencyProperty.RegisterAttached("InitialFocus", typeof(bool), typeof(AttachedProperties), null);
public static void SetInitialFocus(UIElement element, bool value)
{
Control c = element as Control;
if (c != null && value)
c.Focus();
}
public static bool GetInitialFocus(UIElement element)
{
return false;
}
When I put a breakpoint in the SetInitialFocus method, it does fire and the control is indeed the desired TextBox and it does call Focus.
I know other people have created behaviors and such to accomplish this task, but I am wondering why this won't work.
You're right, the Control isn't ready to recieve focus because it hasn't finished loading yet. You can add this to make it work.
public static void SetInitialFocus(UIElement element, bool value)
{
Control c = element as Control;
if (c != null && value)
{
RoutedEventHandler loadedEventHandler = null;
loadedEventHandler = new RoutedEventHandler(delegate
{
// This could also be added in the Loaded event of the MainPage
HtmlPage.Plugin.Focus();
c.Loaded -= loadedEventHandler;
c.Focus();
});
c.Loaded += loadedEventHandler;
}
}
(In some cases, you may need to call ApplyTemplate as well according to this link)
enter code hereI have a ScrollViewer in Silverlight that is not scrolling vertically whenever I call the ScrollToVerticalOffset method from the code behind.
Basically, I have my View (UserControl) that contains the ScrollViewer. I invoke an action from my ViewModel that triggers an event in the View's code-behind that sets the VerticalOffset to a specific value.
First of all, I know this is very ugly. Ideally I wish that I could have an attachable property that I could bind to a property in my ViewModel, that, when set, would cause the VerticalOffset property (which I know is read-only) to be updated, and the ScrollViewer to scroll.
The ScrollViewer contains dynamic content. So, if the user is viewing content in the ScrollViewer, and scrolls half-way down, and then clicks on a button, new content is loaded into the ScrollViewer. The problem is that the ScrollViewer's vertical offset doesn't get reset, so the user has to scroll up to read the content. So, my solution was to be able to control the vertical offset from the ViewModel, and I have racked my brain and can't come up with a viable solution, so I am looking for someone to help, please.
By the way - I have included code from a class I put together for an attachable property. This property binds to a property in my ViewModel. When I set the property in the ViewModel, it correctly triggers the PropertyChanged callback method in this class, which then calls the ScrollToVerticalOffset method for the ScrollViewer, but the ScrollViewer still doesn't scroll.
public class ScrollViewerHelper
{
public static readonly DependencyProperty BindableOffsetProperty =
DependencyProperty.RegisterAttached("BindableOffset", typeof(double), typeof(ScrollViewerHelper),
new PropertyMetadata(OnBindableOffsetChanged));
public static double GetBindableOffset(DependencyObject d)
{
return (double)d.GetValue(BindableOffsetProperty);
}
public static void SetBindableOffset(DependencyObject d, double value)
{
d.SetValue(BindableOffsetProperty, value);
}
private static void OnBindableOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
}
This approach is a little bit funky in my opinion, as I think of both a ScrollViewer and a VerticalScrollOffset as "View" entities that should have very little (or nothing) to do with a ViewModel. It seems like this might be forcing MVVM a little too much, and creating a lot of extra work in creating an attached dependency property and basically trying to keep a bound Offset ViewModel property in sync with the readonly VerticalScrollOffset of the ScrollViewer.
I am not exactly sure of what you are trying to achieve, but it sounds like you are trying to scroll to a specified offset when some dynamic element is added to the underlying panel of your ScrollViewer. Personally, I would just want to handle this behavior with a little bit of code in my View and forget about tying it to the ViewModel.
One really nice way to do this type of thing in Silverlight 3 is with Blend behaviors. You write a little bit of behavior code in C# and then can attach it declaratively to an element in XAML. This keeps it reusable and out of your code-behind. Your project will have to reference the System.Windows.Interactivity DLL which is part of the Blend SKD.
Here's a simple example of a simple Blend behavior you could add to a ScrollViewer which scrolls to a specified offset whenever the size of the underlying content of the ScrollViewer changes:
public class ScrollToOffsetBehavior : Behavior<ScrollViewer>
{
private FrameworkElement contentElement = null;
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register(
"Offset",
typeof(double),
typeof(ScrollToOffsetBehavior),
new PropertyMetadata(0.0));
public double Offset
{
get { return (double)GetValue(OffsetProperty); }
set { SetValue(OffsetProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (this.AssociatedObject != null)
{
this.AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.contentElement != null)
{
this.contentElement.SizeChanged -= contentElement_SizeChanged;
}
if (this.AssociatedObject != null)
{
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
this.contentElement = this.AssociatedObject.Content as FrameworkElement;
if (this.contentElement != null)
{
this.contentElement.SizeChanged += new SizeChangedEventHandler(contentElement_SizeChanged);
}
}
void contentElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.AssociatedObject.ScrollToVerticalOffset(this.Offset);
}
}
You could then apply this behavior to the ScrollViewer in XAML (and specify an offset of 0 to scroll back to the top):
<ScrollViewer>
<i:Interaction.Behaviors>
<local:ScrollToOffsetBehavior Offset="0"/>
</i:Interaction.Behaviors>
...Scroll Viewer Content...
</ScrollViewer>
This would be assuming that you always want to scroll to the offset whenever the content size changes. This may not be exactly what you are looking for, but it is one example of how something like this can be done in the view using a behavior.