I have some items in a WrapPanel. I want to be able to click on an item and have it expand to the full width of the wrap panel. I tried doing this by creating two states, Expanded and Colapsed, in the control that is used for each item. For the Expanded state, I bound the Width of the control to be equal to the ActualWidth of the WrapPanel.
When I didn't get the result I expected, I tried setting Expanded value to a specific number (instead of the the binding). That is working. The items toggle between the two Colapsed and Exapanded widths. I still want to have the Expanded state be equal to the width of the WrapPanel though, not an arbitrary fixed width. I know my binding works because if I just bind the Width property directly (not via visual states), the items in the WrapPanel match its width.
Expanded state with Binding - Doesn't work:
<VisualState x:Name="Expanded">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="JobMaster">
<EasingDoubleKeyFrame KeyTime="0">
<EasingDoubleKeyFrame.Value>
<Binding
Path="ActualWidth"
RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}" />
</EasingDoubleKeyFrame.Value>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
Expanded State with hard coded value - Works
<VisualState x:Name="Expanded">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="JobMaster">
<EasingDoubleKeyFrame KeyTime="0" Value="800" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
Bind the control Width property directly works
<UserControl.Width>
<Binding
Path="ActualWidth"
RelativeSource="{RelativeSource AncestorType={x:Type WrapPanel}}" />
</UserControl.Width>
So why doesn't the Binding in the state work or is there another way to do this?
I was never able to get this working using visual states. Instead I wrote a behavoir.
public class TileExpandColapseBehavoir : Behavior<Control>
{
private ITile _data;
#region Properties
public static readonly DependencyProperty TileControlProperty = DependencyProperty.Register("TileControl", typeof(object), typeof(TileExpandColapseBehavoir), new PropertyMetadata(null));
public static readonly DependencyProperty DefaultWidthProperty = DependencyProperty.Register("DefaultWidth", typeof(Double), typeof(TileExpandColapseBehavoir), new PropertyMetadata(null));
public object TileControl
{
get { return (object)this.GetValue(TileControlProperty); }
set { this.SetValue(TileControlProperty, value); }
}
public double DefaultWidth
{
get { return (double)this.GetValue(DefaultWidthProperty); }
set { this.SetValue(DefaultWidthProperty, value); }
}
#endregion
public TileExpandColapseBehavoir()
{
}
protected override void OnAttached()
{
this.AssociatedObject.PreviewMouseDown +=new MouseButtonEventHandler(AssociatedObject_MouseUp);
}
private void AssociatedObject_MouseUp(object sender, MouseButtonEventArgs e)
{
UIElement child = (UIElement)sender;
WrapPanel parentWrap = FindAncestorUtil.TryFindAcestor<WrapPanel>(child);
if (parentWrap != null && TileControl is UserControl)
{
GetData();
if (_data.IsExpanded == false)
{
Binding newBinding = new Binding();
newBinding.Source = parentWrap;
newBinding.Path = new PropertyPath("ActualWidth");
UserControl thisTile = (UserControl)TileControl;
BindingOperations.SetBinding(thisTile, UserControl.WidthProperty, newBinding);
_data.IsExpanded = true;
}
else
{
UserControl thisTile = (UserControl)TileControl;
BindingOperations.ClearBinding(thisTile, UserControl.WidthProperty);
thisTile.Width = DefaultWidth;
_data.IsExpanded = false;
}
}
}
private void GetData()
{
if (_data == null && AssociatedObject.DataContext is ITile)
{
_data = (ITile)AssociatedObject.DataContext;
}
}
}
Your RelativeSource binding is looking for an ancestor of the animation, not the target of the animation. Try giving your WrapPanel a name and use Element binding instead.
<Binding Path="ActualWidth" ElementName="MyWrapPanel"/>
Related
Intro
I'm working on an application and I want to be able to change the language when the app is running. For cross-platform compatibility I'm using AvaloniaUI.
I've found a few helpful articles:
Simple localization in WPF
Simple localization in WPF, extended for multiple resource-files
Answer to question on StackOverflow (basically the first link)
The problem
On startup of the app a binding is created (in LocExtensionWithMultipleResxFiles) between my control on the view and string this[string key] ( in TranslationSourceWithMultipleResxFiles). The app correctly loads the translations on startup.
On my View I have a button, the ClickEvent correctly sets TranslationSourceWithMultipleResxFiles.Instance.CurrentCulture but the text in my view doesn't update. I'm not sure where I did something wrong or if I need to change the code somewhere, so any help is appreciated.
My code
Using the above articles I have the following code:
TranslationSourceWithMultipleResxFiles contains a Dictionary for all the ResourceManagers that are used. string this[string key] returns the translated text. CurrentCulture is the property you set to change the Culture.
public class TranslationSourceWithMultipleResxFiles : INotifyPropertyChanged
{
public static TranslationSourceWithMultipleResxFiles Instance { get; } = new TranslationSourceWithMultipleResxFiles();
private readonly Dictionary<string, ResourceManager> resourceManagerDictionary = new Dictionary<string, ResourceManager>();
// key is the baseName + stringName that is binded to, this returns the translated text.
public string this[string key]
{
get
{
var (baseName, stringName) = SplitName(key);
string? translation = null;
if (resourceManagerDictionary.ContainsKey(baseName))
translation = resourceManagerDictionary[baseName].GetString(stringName, currentCulture);
return translation ?? key;
}
}
// the culture TranslationSourceWithMultipleResxFiles uses for translations.
private CultureInfo currentCulture = CultureInfo.InstalledUICulture;
public CultureInfo CurrentCulture
{
get { return currentCulture; }
set
{
if (currentCulture != value)
{
currentCulture = value;
NotifyPropertyChanged(string.Empty); // string.Empty/null indicates that all properties have changed
}
}
}
// WPF bindings register PropertyChanged event if the object supports it and update themselves when it is raised
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public void AddResourceManager(ResourceManager resourceManager)
{
if (!resourceManagerDictionary.ContainsKey(resourceManager.BaseName))
resourceManagerDictionary.Add(resourceManager.BaseName, resourceManager);
}
public static (string baseName, string stringName) SplitName(string name)
{
int idx = name.LastIndexOf('.');
return (name.Substring(0, idx), name.Substring(idx + 1));
}
}
In xaml you set the Translation.ResourceManager per UserContorl/Window etc. This is used so multiple resource files can be used in the application. Each child Control looks to this ResourceManager for their translations.
public class Translation : AvaloniaObject
{
public static readonly AttachedProperty<ResourceManager> ResourceManagerProperty = AvaloniaProperty.RegisterAttached<Translation, AvaloniaObject, ResourceManager>("ResourceManager");
public static ResourceManager GetResourceManager(AvaloniaObject dependencyObject)
{
return (ResourceManager)dependencyObject.GetValue(ResourceManagerProperty);
}
public static void SetResourceManager(AvaloniaObject dependencyObject, ResourceManager value)
{
dependencyObject.SetValue(ResourceManagerProperty, value);
}
}
Creates a Binding between the Control on the view and the correct ResourceManager.
public class LocExtensionWithMultipleResxFiles : MarkupExtension
{
public string StringName { get; } // Key name of the translation in a resource file.
public LocExtensionWithMultipleResxFiles(string stringName)
{
StringName = stringName;
}
// Find out what ResourceManager this control uses
private ResourceManager? GetResourceManager(object control)
{
if (control is AvaloniaObject dependencyObject)
{
object localValue = dependencyObject.GetValue(Translation.ResourceManagerProperty);
if (localValue != AvaloniaProperty.UnsetValue)
{
if (localValue is ResourceManager resourceManager)
{
TranslationSourceWithMultipleResxFiles.Instance.AddResourceManager(resourceManager);
return resourceManager;
}
}
}
return null;
}
// Create a binding between the Control and the translated text in a resource file.
public override object ProvideValue(IServiceProvider serviceProvider)
{
object? targetObject = (serviceProvider as IProvideValueTarget)?.TargetObject;
if (targetObject?.GetType().Name == "SharedDp") // is extension used in a control template?
return targetObject; // required for template re-binding
string baseName = GetResourceManager(targetObject)?.BaseName ?? string.Empty; // if the targetObject has a ResourceManager set, BaseName is set
if (string.IsNullOrEmpty(baseName)) // if the targetobjest doesnt have a RM set, it gets the root elements RM.
{
// rootObject is the root control of the visual tree (the top parent of targetObject)
object? rootObject = (serviceProvider as IRootObjectProvider)?.RootObject;
baseName = GetResourceManager(rootObject)?.BaseName ?? string.Empty;
}
if (string.IsNullOrEmpty(baseName)) // template re-binding
{
if (targetObject is Control frameworkElement)
baseName = GetResourceManager(frameworkElement.TemplatedParent)?.BaseName ?? string.Empty;
}
// create a binding between the Control and the correct resource-file
var binding = new ReflectionBindingExtension
{
Mode = BindingMode.OneWay,
Path = $"[{baseName}.{StringName}]", // This is the ResourceManager.Key
Source = TranslationSourceWithMultipleResxFiles.Instance,
FallbackValue = "Fallback, can't set translation.",
TargetNullValue = StringName,
};
return binding.ProvideValue(serviceProvider);
}
}
My View
<Window <!-- Standard Window xaml -->
xmlns:l="clr-namespace:TestAppForMVVMwithBaseClasses.Localization"
l:Translation.ResourceManager="{x:Static p:Resources.ResourceManager}">
<StackPanel>
<TextBlock Text="{l:LocExtensionWithMultipleResxFiles String1}"/>
<Button Content="Nl" Click="CurrentCultureNl_Click"/>
<Button Content="En" Click="CurrentCultureEn_Click"/>
</StackPanel>
</Window>
I have a custom dependency property on my control like so (boilerplate to implement the control left out):
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(String),
typeof(BindingTestControl),
new PropertyMetadata(null));
public static void SetValue(UIElement element, string value)
{
element.SetValue(ValueProperty, value);
}
public static string GetValue(UIElement element)
{
return (string)element.GetValue(ValueProperty);
}
I created a page with code-behind to bind to with relevant xaml like so (with x:Name="root" on the page):
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:BindingTestControl Value="{Binding ElementName=root, Path=StringItem}"/>
<Button Width="200" Height="100" Tapped="Button_Tapped" FlowDirection="RightToLeft"/>
</Grid>
With code-behind like so (again, only relevant parts shown):
private string stringItem = "";
public string StringItem
{
get
{
return stringItem;
}
set
{
this.stringItem = value;
OnPropertyChanged("StringItem");
}
}
int i = 0;
private void Button_Tapped(object sender, TappedRoutedEventArgs e)
{
//i++;
this.StringItem = "Test" + i;
}
This works fine the first time, but if I update the value in the Textbox, the binding won't overwrite the new value. If I uncomment the i++; then the binding overwrites every time. I'm assuming this happens this way because the value that is being sent with INotifyPropertyChanged is the same as the previous one despite the value in the Textbox no longer being the same.
Is there a way to force the value through the binding even if it hasn't changed?
You could change your setter to something like -
set
{
this.stringItem = null;
this.stringItem = value;
OnPropertyChanged("StringItem");
}
Which should force the PropertyChanged event to fire as a change in value has occurred.
What is the easiest way to bind to View's (or any other Android control) weight? Because this property doesn't have a setter, I tried custom binding, but id doesn't seem to work:
public class ViewWeightCustomBinding : MvxAndroidTargetBinding
{
public ViewWeightCustomBinding(object target) : base(target)
{
}
public override Type TargetType
{
get { return typeof (int); }
}
protected override void SetValueImpl(object target, object value)
{
var realTarget = target as View;
if(target == null)
return;
ViewGroup.LayoutParams layoutParameters = realTarget.LayoutParameters;
realTarget.LayoutParameters = new LinearLayout.LayoutParams(layoutParameters.Width, layoutParameters.Height,
(int) value);
}
}
registration in setup:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterFactory(new MvxSimplePropertyInfoTargetBindingFactory(typeof(ViewWeightCustomBinding), typeof(View), "ViewWeight"));
base.FillTargetFactories(registry);
}
And .axml
<View
android:layout_width="0dp"
android:layout_height="3dp"
android:background="#color/green_holo"
local:MvxBind="ViewWeight Id" />
I can see Waring in debug window:
[0:]
MvxBind:Warning: 5.20 Failed to create target binding for binding ViewWeight for Id
[0:] MvxBind:Warning: 5.20 Failed to create target binding for binding ViewWeight for Id
01-31 10:54:57.247 I/mono-stdout( 3795): MvxBind:Warning: 5.20 Failed to create target binding for binding ViewWeight for Id
MvxSimplePropertyInfoTargetBindingFactory can only be used for real C# properties.
For invented "pseudo" properties, you need to use a custom binding registration like that shown in the n=28 tutorial -
protected override void FillTargetFactories(Cirrious.MvvmCross.Binding.Bindings.Target.Construction.IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterCustomBindingFactory<BinaryEdit>(
"N28",
binary => new BinaryEditFooTargetBinding(binary) );
base.FillTargetFactories(registry);
}
https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-28-CustomBinding/CustomBinding.Droid/Setup.cs
i created a RadioGroupSelectedIndexBinding from the source of MvxRadioGroupSelectedItemBinding.
It works ok, but not at viewmodel startup: the binding from viewmodel to view is called but at this time the RadioGroup has no child views. It seems they haven't been inflated yet.
This is a "bug" (or feature :p) in Mvvmcross custom inflater/binding ? Or is there something to overload in MvxAndroidTargetBinding ?
Edit: code of MvxRadioGroupSelectedIndexBinding (Index, not Item: different from MvxRadioGroupSelectedItemBinding).
public class MvxRadioGroupSelectedIndexBinding : MvxAndroidTargetBinding
{
bool stopListeningCheckChanged = false;
private int selectedIndex = -2;
public int SelectedIndex
{
get { return selectedIndex; }
set { if(value != selectedIndex) { selectedIndex = value; FireValueChanged(SelectedIndex); } }
}
public static void Register(IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterCustomBindingFactory<RadioGroup>("SelectedIndex", radioGroup => new MvxRadioGroupSelectedIndexBinding(radioGroup));
}
public MvxRadioGroupSelectedIndexBinding(RadioGroup radioGroup) : base(radioGroup)
{
if (radioGroup == null)
{
Mvx.Trace(MvxTraceLevel.Error, "RadioGroup SelectedIndex: radioGroup is null");
return;
}
radioGroup.CheckedChange += CheckedChange;
radioGroup.ChildViewAdded += RadioGroupOnChildViewAdded;
}
private void RadioGroupOnChildViewAdded(object sender, ViewGroup.ChildViewAddedEventArgs childViewAddedEventArgs)
{
var radioGroup = Target as RadioGroup;
if (selectedIndex == radioGroup.ChildCount-1)
{
stopListeningCheckChanged = true;
radioGroup.Check(radioGroup.GetChildAt(selectedIndex).Id);
stopListeningCheckChanged = false;
}
}
private void CheckedChange(object sender, RadioGroup.CheckedChangeEventArgs e)
{
if (stopListeningCheckChanged)
return;
var radioGroup = Target as RadioGroup;
var checkedId = e.CheckedId;
if (checkedId == View.NoId)
{
SelectedIndex = -1;
return;
}
for (var i = radioGroup.ChildCount - 1; i >= 0; i--)
{
if (checkedId == radioGroup.GetChildAt(i).Id)
{
SelectedIndex = i;
return;
}
}
SelectedIndex = -1;
Mvx.Trace(MvxTraceLevel.Error, "RadioGroup id not found: {0}", checkedId);
}
public override void SetValue(object index)
{
var radioGroup = Target as RadioGroup;
if (radioGroup == null)
return;
stopListeningCheckChanged = true;
selectedIndex = (int)index;
if (selectedIndex < 0 || selectedIndex >= radioGroup.ChildCount)
{
radioGroup.ClearCheck();
}
else
{
radioGroup.Check(radioGroup.GetChildAt(selectedIndex).Id);
}
stopListeningCheckChanged = false;
}
public override Type TargetType
{
get { return typeof(object); }
}
protected override void SetValueImpl(object target, object value)
{
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.TwoWay; }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
var radioGroup = Target as RadioGroup;
if (radioGroup != null)
{
radioGroup.CheckedChange -= CheckedChange;
radioGroup.ChildViewAdded -= RadioGroupOnChildViewAdded;
}
}
base.Dispose(isDisposing);
}
}
And usage
<RadioGroup
android:orientation="horizontal"
local:MvxBind="SelectedIndex SelectedChoiceIndex">
<RadioButton
android:text="choice 1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RadioButton
android:text="choice 2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RadioGroup>
The "bug" is that when SetValue is called, the RadioGroup has no childs. I suppose the custom inflater create and binds views at the same time. In fact it should bind views only when their childs are inflated. I may be wrong though, i've not checked the source code. And it could have other bad side effects.
This is a "bug" (or feature :p) in Mvvmcross custom inflater/binding ?
Neither - I think this is simply out of scope of what the MvxRadioGroup was designed to target.
MvxRadioGroupSelectedItemBinding was a user contribution and I believe it was designed to be used exactly as shown in https://github.com/MvvmCross/MvvmCross-Tutorials/pull/8:
<MvxRadioGroup
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxItemTemplate="#layout/item_radio"
local:MvxBind="ItemsSource Items;SelectedItem SelectedItem"
/>
=> so it is designed to work for lists of items where the ItemsSource is first set, and then the SelectedItem is also set.
I don't believe the author of that MvxRadioGroup had any intention of supporting AXML based lists of radio choices.
With that said, if anyone wants to author a more extensive RadioGroup solution - e.g. one that can cope with AXML defined lists or that can cope with ItemsSource changes after SelectedItem has been set, then I think this should be perfectly possible to do - and it looks like you've already gone a long way towards this :) To "perfectly" handle all combinations of dynamic and static list changes, would probably require using some kind of technique which rechecks the SelectedItem property each and every time the ItemsSource and/or static items have been added. For practical reasons I think this recheck would need to be performed within some custom RadioGroup and/or binding based code - I don't think there's any way to do this within the XML inflation handlers as Android simply doesn't present any suitable childrenInflatedAndAdded type callbacks during inflation.
It may also be interesting to note that XAML presents similar challenges in this area - e.g. see Silverlight XAML Attribute Definition Order Matters for a XAML ComboBox scenario where the items must be set before the selected item.
I need WatermarkPasswordBox control but there is not in Winrt. Maybe we can add Watermark property to PasswordBox. Are there anyone who can do it?
Thanks
The purpose of a watermark is to convey a message behind the control. In the case of this demonstration, the watermark also dissappear after you start typing text, so they are more like a field "hint" telling you what is expected.
To achieve this, we turn to a regular WPF solution provider, the AttachedProperty. AttachedProperties allow you to add extra properties to any control. You can also extend it into an Attachedbehaviour, where you are making the control react to changes to the property.
In this example, we use two attached properties. The first "WaterrmarkProperty" to take the watermark value and initialise the control:
public static string GetWatermark(DependencyObject obj)
{
return (string)obj.GetValue(WatermarkProperty);
}
public static void SetWatermark(DependencyObject obj, string value)
{
obj.SetValue(WatermarkProperty, value);
}
public static readonly DependencyProperty WatermarkProperty =
DependencyProperty.RegisterAttached("Watermark", typeof(string), typeof(TextBoxHelper), new UIPropertyMetadata(null, WatermarkChanged));
The second attached property is to notify whether there is a value in the box, which the template binds to and hides or shows the watermark.
public static bool GetShowWatermark(DependencyObject obj)
{
return (bool)obj.GetValue(ShowWatermarkProperty);
}
public static void SetShowWatermark(DependencyObject obj, bool value)
{
obj.SetValue(ShowWatermarkProperty, value);
}
public static readonly DependencyProperty ShowWatermarkProperty =
DependencyProperty.RegisterAttached("ShowWatermark", typeof(bool), typeof(TextBoxHelper), new UIPropertyMetadata(false));
For the TextBoxHelper, whenever the text is changed, the watermark is shown or hidden as follows:
private static void CheckShowWatermark(TextBox box)
{
box.SetValue(TextBoxHelper.ShowWatermarkProperty, box.Text == string.Empty);
}
This is controlled by the ControlTemplate:
<ControlTemplate x:Key="WatermarkedTextBoxTemplate" TargetType="{x:Type TextBox}">
<Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">
<Grid>
<TextBlock Text="{Binding Path=(local:TextBoxHelper.Watermark), RelativeSource={RelativeSource TemplatedParent}}" Opacity=".5" FontWeight="Bold" Visibility="{Binding (local:TextBoxHelper.ShowWatermark), Converter={StaticResource BooleanToVisibilityConverter}, RelativeSource={RelativeSource TemplatedParent}}" />
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</Microsoft_Windows_Themes:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Source: http://code.msdn.microsoft.com/windowsdesktop/Watermarked-TextBox-and-444ebdec
In Windows 8.0 you can use the WatermarkPasswordBox from WinRT XAML Toolkit, which you can get from here. It gives you a Watermark property to set any UI element (Shape, Image, etc.) as a watermark or WatermarkText property that takes a text and WatermarkTextStyle that takes a TextBlock Style to style the text.
In Windows 8.1 you can use the same or use the new PlaceholderText property.
Feel free to rip out and modify the WatermarkPasswordBox control's code from the library and use it in your app. It's MIT licensed. No credits required. Just take the .cs and .xaml files and include the .xaml resource dictionary in your Themes/Generic.xaml like so:
<ResourceDictionary
Source="ms-appx:///YourControlsLibraryNamefNotInMainApp/RelativeDirectoryPathOfTheFile/WatermarkPasswordBox.xaml" />
UPDATE 1
If you don't want to use 3rd party DLL, add these two methods in PasswordBoxBehavior.cs file.
using System.Reflection;
public static T FindVisualChildByName<T>(this DependencyObject fe, string name) where T : DependencyObject
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException("name");
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(fe); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(fe, i);
string a = child.GetValue(FrameworkElement.NameProperty) as string;
if (a == name)
{
return child as T;
}
T t = FindVisualChildByName<T>(child, name);
if (t != null)
{
return t;
}
}
return default(T);
}
public static T FindVisualParent<T>(this DependencyObject fe) where T : DependencyObject
{
for (fe = VisualTreeHelper.GetParent(fe); fe != null; fe = VisualTreeHelper.GetParent(fe))
{
T t = fe as T;
if (t != null)
{
return t;
}
}
return default(T);
}
Here's extensive blog from JulMar
Adding a watermark to a PasswordBox in a Windows Store app
Here’s the code if you’d like to use it yourself.