Change SliderThumbStyle value from Slider definition - wpf

I have this SliderThumbStyle:
<Style x:Key="SliderThumbStyle" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Grid>
<Border Name="outerBorder"
Background="{DynamicResource ApplicationBorderBrush}"
BorderBrush="{DynamicResource ApplicationBorderBrush}"
Height="24"
Width="24"
Opacity="1"
BorderThickness="2"
CornerRadius="10"/>
<TextBlock x:Name="sliderValue"
FontSize="10"
Foreground="Silver"
Text="{Binding Value, RelativeSource={RelativeSource AncestorType=Slider}, StringFormat={}{0:N1}}"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In my application i am using this Slider Style twice but one of them not needed this N1 StringFormat but N0for only integer values.
Any idea how to choose this when i define my Slider in advanced ?
As mm8 suggestive i try this:
<Slider Tag="{Binding Value, RelativeSource={RelativeSource Self}, StringFormat=N1}" />
Style:
Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=Slider}}"/>
But it seems that it now show the value in N1 format but 1.23456789
I also try this:
Tag="{Binding Value, RelativeSource={RelativeSource Self}, StringFormat={}{0:N1}}"

I am afraid you can't change the StringFormat without modifying the Style. What you could do is to bind to the Tag property of the Slider in your Style:
<TextBlock x:Name="sliderValue"
FontSize="10"
Foreground="Silver"
Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=Slider}}"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
...and then handle the ValueChanged event of each individual Slider and set the Tag property to a formatted string:
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Slider slider = (Slider)sender;
slider.Tag = slider.Value.ToString("N1");
}
You may want to wrap this functionality an attached behaviour:
public class SliderFormatBehavior
{
public static string GetStringFormat(Slider treeViewItem)
{
return (string)treeViewItem.GetValue(StringFormatProperty);
}
public static void SetStringFormat(Slider slider, string value)
{
slider.SetValue(StringFormatProperty, value);
}
public static readonly DependencyProperty StringFormatProperty =
DependencyProperty.RegisterAttached(
"StringFormat",
typeof(string),
typeof(SliderFormatBehavior),
new UIPropertyMetadata(null, OnStringFormatChanged));
static void OnStringFormatChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
Slider slider = depObj as Slider;
if (slider != null)
{
if (slider.IsLoaded)
{
SetTag(slider);
}
else
{
slider.Loaded += Slider_Loaded;
}
slider.ValueChanged += Slider_ValueChanged;
}
}
private static void Slider_Loaded(object sender, RoutedEventArgs e)
{
Slider slider = (Slider)sender;
SetTag(slider);
slider.Loaded -= Slider_Loaded;
}
private static void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
SetTag((Slider)sender);
}
private static void SetTag(Slider slider)
{
slider.Tag = slider.Value.ToString(GetStringFormat(slider));
}
}
Sample usage:
<Slider ... local:SliderFormatBehavior.StringFormat="N1" />

Related

How To Search In ComboBox With TextBox?

I Have a ComboBox With 3 Items (Room,Class,HighSchool) And a TextBox.
I Want When i Write Room IN TextBox Immediately Combobox.
SelectedItem = Room
You'd be better off using a custom control. I use an intellisense text box that you bind to a list, when you start typing it will automatically select any item in the list with that character.
window xaml below;
<UserControl.Resources>
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="_Border"
Padding="2"
SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="_Border" Property="Background" Value="Gray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<TextBox Name="textbox" Grid.Column="1" Grid.Row="2" GotMouseCapture="textbox_GotMouseCapture"
GotFocus="textbox_GotFocus" PreviewKeyDown="textbox_PreviewKeyDown" TextChanged="textbox_TextChanged"
DataContext="{Binding ElementName=intelliWin}"
Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="{Binding ElementName=intelliWin, Path=Width}" TextAlignment="Center" Height="20"/>
<Popup Name="popup" Height="Auto" Width="Auto" MinWidth="180" StaysOpen="False" Placement="Bottom"
PlacementTarget="{Binding ElementName=textbox}" HorizontalAlignment="Left">
<Popup.Style>
<Style TargetType="Popup">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="IsOpen" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Popup.Style>
<Grid>
<ListBox Name="listbox" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
MouseUp="listbox_MouseUp" ItemContainerStyle="{StaticResource ListBoxItemStyle}">
</ListBox>
</Grid>
</Popup>
</Grid>
to use it in the window you need to do this
<local:IntellisenseStyleTextbox Grid.Column="1" Grid.Row="6" Text="{Binding YOUR LIST DATA, UpdateSourceTrigger=PropertyChanged}" x:Name="intelliseText" Width="200" HorizontalContentAlignment="Center"/>'
then add this to the .cs file
public partial class IntellisenseStyleTextbox : UserControl, INotifyPropertyChanged
{
public IntellisenseStyleTextbox()
{
InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
public List<string> PossibleItems { get; set; }
public event EventHandler ItemSelected;
protected void OnItemSelected(EventArgs e)
{
EventHandler handler = ItemSelected;
if (handler != null)
{
handler(this, e);
}
}
// Bindable Text property from http://blog.jerrynixon.com/2013/07/solved-two-way-binding-inside-user.html
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValueDp(TextProperty, value);
}
}
// Required to allow items to bind to Text property
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string),
typeof(IntellisenseStyleTextbox), null);
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value,
[System.Runtime.CompilerServices.CallerMemberName] string p = null)
{
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public List<string> GetDisplayedModelNames()
{
// Only display items that have the current text at the start of them
List<string> toDisplay = new List<string>();
string match = textbox.Text.ToLower();
foreach (string possibleItem in PossibleItems)
{
string lower = possibleItem.ToLower();
if (lower.Contains(match))
{
toDisplay.Add(possibleItem);
}
}
return toDisplay;
}
private void textbox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
switch (e.Key)
{
// Up and down arrow keys move up and down list box
case Key.Down:
listbox.SelectedIndex++;
listbox.ScrollIntoView(listbox.SelectedItem);
break;
case Key.Up:
// Make sure item is always selected
if (listbox.SelectedIndex > 0)
{
listbox.SelectedIndex--;
listbox.ScrollIntoView(listbox.SelectedItem);
}
break;
// Enter key fills in OutputName with selected text
case Key.Enter:
SelectListBoxItem();
break;
case Key.Back:
popup.IsOpen = true;
break;
}
base.OnPreviewKeyDown(e);
}
private void SelectListBoxItem()
{
if (listbox.SelectedIndex < 0) return;
List<string> outputNames = GetDisplayedModelNames();
if (listbox.SelectedIndex < outputNames.Count)
{
this.Text = outputNames[listbox.SelectedIndex];
// Move caret to end of text
textbox.CaretIndex = this.Text.Count();
popup.IsOpen = false;
OnItemSelected(new EventArgs());
}
}
private void listbox_MouseUp(object sender, MouseButtonEventArgs e)
{
SelectListBoxItem();
}
private void textbox_GotMouseCapture(object sender, MouseEventArgs e)
{
OpenPopup();
}
private void textbox_GotFocus(object sender, RoutedEventArgs e)
{
OpenPopup();
}
private void OpenPopup()
{
listbox.ItemsSource = GetDisplayedModelNames();
popup.IsOpen = true;
}
private void textbox_TextChanged(object sender, TextChangedEventArgs e)
{
OnPropertyChanged(new DependencyPropertyChangedEventArgs(TextProperty, textbox.Text, textbox.Text));
// Update listbox with relevant names
if (PossibleItems == null) return;
listbox.ItemsSource = this.GetDisplayedModelNames();
}
}

Wpf, How to drag object inside draggable element?

I am developing WPF application. I should implement designer page with draggable objects inside other draggable objects. Look at the picture:
Now my Drag and Drop 1 and 2 working, but when I am trying to drag Inner object 1 and execute Drag and Drop 3 it is not working properly. Instead of drag Inner object 1, I am dragging Master object 1.
My xaml code:
<ItemsControl ItemsSource="{Binding MasterObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Width" Value="{Binding Path=Width}" />
<Setter Property="Height" Value="{Binding Path=Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid behaviors:DragBehavior.Drag="True">
<Rectangle Fill="LightGray" Stroke="SlateGray" StrokeThickness="1" />
<TextBlock Text="{Binding Name}" />
<ItemsControl ItemsSource="{Binding InnerObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Width" Value="{Binding Path=Width}" />
<Setter Property="Height" Value="{Binding Path=Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid behaviors:DragBehavior.Drag="True">
<Rectangle Fill="LightGray" Stroke="SlateGray" StrokeThickness="1" />
<TextBlock Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
And my DragBehavior class:
public class DragBehavior
{
public readonly TranslateTransform Transform = new TranslateTransform();
private Point _elementStartPosition2;
private Point _mouseStartPosition2;
private static DragBehavior _instance = new DragBehavior();
public static DragBehavior Instance
{
get { return _instance; }
set { _instance = value; }
}
public static bool GetDrag(DependencyObject obj)
{
return (bool)obj.GetValue(IsDragProperty);
}
public static void SetDrag(DependencyObject obj, bool value)
{
obj.SetValue(IsDragProperty, value);
}
public static readonly DependencyProperty IsDragProperty =
DependencyProperty.RegisterAttached(
"Drag",
typeof(bool),
typeof(DragBehavior),
new PropertyMetadata(false, OnDragChanged));
private static void OnDragChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// ignoring error checking
var element = (UIElement)sender;
var isDrag = (bool)(e.NewValue);
Instance = new DragBehavior();
((UIElement)sender).RenderTransform = Instance.Transform;
if (isDrag)
{
element.MouseLeftButtonDown += Instance.ElementOnMouseLeftButtonDown;
element.MouseLeftButtonUp += Instance.ElementOnMouseLeftButtonUp;
element.MouseMove += Instance.ElementOnMouseMove;
}
else
{
element.MouseLeftButtonDown -= Instance.ElementOnMouseLeftButtonDown;
element.MouseLeftButtonUp -= Instance.ElementOnMouseLeftButtonUp;
element.MouseMove -= Instance.ElementOnMouseMove;
}
}
private void ElementOnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
var parent = Application.Current.MainWindow;
_mouseStartPosition2 = mouseButtonEventArgs.GetPosition(parent);
((UIElement)sender).CaptureMouse();
}
private void ElementOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
((UIElement)sender).ReleaseMouseCapture();
_elementStartPosition2.X = Transform.X;
_elementStartPosition2.Y = Transform.Y;
}
private void ElementOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var parent = Application.Current.MainWindow;
var mousePos = mouseEventArgs.GetPosition(parent);
var diff = (mousePos - _mouseStartPosition2);
if (!((UIElement)sender).IsMouseCaptured) return;
Transform.X = _elementStartPosition2.X + diff.X;
Transform.Y = _elementStartPosition2.Y + diff.Y;
}
}
I guess I should to fix DragBehaviour class, but I don't have any idea how. Now I'm really confused.
The MouseLeftButtonDown event is bubbling from the inner container to the parent one. Try setting the e.Handled property to true in your event handler. In this case, only the handler for the first clicked element will be raised and your code should work properly.
I also suggest that you use an attached behavior instead of a class with several attached properties. You can find it in a free MVVM Framework provided by DevExpress.
As a rule behaviors give you much more freedom in such scenarios.

Wpf disable repeatbuttons when scrolled to top/bottom

I'm making a touchscreen interface that uses a listbox.
I have a button above and below the listbox for page up/down.
I'm trying to get it to where when scrolled all the way up the pageup button gets disabled.
and when scrolled all the way down the pagedown button gets disabled too.
Here's the code in my Styles.xaml for the Listbox
<Style x:Key="{x:Type ListBox}" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Key="{x:Type ListBox}" TargetType="{x:Type ListBox}">
<DockPanel>
<RepeatButton x:Name="LineUpButton" DockPanel.Dock="Top"
HorizontalAlignment="Stretch"
Height="50"
Content="/\"
Command="{x:Static ScrollBar.PageUpCommand}"
CommandTarget="{Binding ElementName=scrollviewer}" />
<RepeatButton x:Name="LineDownButton" DockPanel.Dock="Bottom"
HorizontalAlignment="Stretch"
Height="50"
Content="\/"
Command="{x:Static ScrollBar.PageDownCommand}"
CommandTarget="{Binding ElementName=scrollviewer}" />
<Border BorderThickness="1" BorderBrush="Gray" Background="White">
<ScrollViewer x:Name="scrollviewer">
<ItemsPresenter/>
</ScrollViewer>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
</Style>
And here's where I instantiate the listbox
<ListBox SelectedItem="{Binding SelectedCan}" ItemsSource="{Binding Path=SelectedKioskCashCans}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding image}" MaxWidth="75" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I searched all around yesterday with no luck.
I'm hoping to be able to do it all in xaml.
I'm using images for the buttons but took them out for readability above,
they really look like...
<RepeatButton x:Name="LineUpButton" DockPanel.Dock="Top" HorizontalAlignment="Stretch"
Height="50"
Command="{x:Static ScrollBar.PageUpCommand}"
CommandTarget="{Binding ElementName=scrollviewer}">
<RepeatButton.Template>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Grid>
<Image Name="Normal" Source="/Images/up.png"/>
<Image Name="Pressed" Source="/Images/up.png" Visibility="Hidden"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Normal" Property="Visibility" Value="Hidden"/>
<Setter TargetName="Pressed" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
Just use CanExecute method of the PageUpCommand for that. Return false if where are no pages left and the button will become disabled automatically.
EDIT:
I have created a simple attached behavior that can be used to fix this problem. Just set the following attached property on the ScrollViewer:
<ScrollViewer x:Name="scrollviewer"
z:ScrollBarCommandsCanExecuteFixBehavior.IsEnabled="True">
<ItemsPresenter/>
</ScrollViewer>
And here is the source code of the behavior:
public static class ScrollBarCommandsCanExecuteFixBehavior
{
#region Nested Types
public class CommandCanExecuteMonitor<T> where T : UIElement
{
protected T Target { get; private set; }
protected CommandCanExecuteMonitor(T target, RoutedCommand command)
{
Target = target;
var binding = new CommandBinding(command);
binding.CanExecute += OnCanExecute;
target.CommandBindings.Add(binding);
}
protected virtual void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
}
}
public class PageUpCanExecuteMonitor : CommandCanExecuteMonitor<ScrollViewer>
{
public PageUpCanExecuteMonitor(ScrollViewer scrollViewer)
: base(scrollViewer, ScrollBar.PageUpCommand)
{
}
protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Handled)
{
return;
}
if (Equals(Target.VerticalOffset, 0.0))
{
e.CanExecute = false;
e.Handled = true;
}
}
}
public class PageDownCanExecuteMonitor : CommandCanExecuteMonitor<ScrollViewer>
{
public PageDownCanExecuteMonitor(ScrollViewer scrollViewer)
: base(scrollViewer, ScrollBar.PageDownCommand)
{
}
protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Handled)
{
return;
}
if (Equals(Target.VerticalOffset, Target.ScrollableHeight))
{
e.CanExecute = false;
e.Handled = true;
}
}
}
#endregion
#region IsEnabled Attached Property
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool) obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof (bool), typeof (ScrollBarCommandsCanExecuteFixBehavior), new PropertyMetadata(false, OnIsEnabledChanged));
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool) e.NewValue)
{
var scrollViewer = d as ScrollViewer;
if (scrollViewer != null)
{
OnAttached(scrollViewer);
}
else
{
throw new NotSupportedException("This behavior only supports ScrollViewer instances.");
}
}
}
private static void OnAttached(ScrollViewer target)
{
SetPageUpCanExecuteMonitor(target, new PageUpCanExecuteMonitor(target));
SetPageDownCanExecuteMonitor(target, new PageDownCanExecuteMonitor(target));
}
#endregion
#region PageUpCanExecuteMonitor Attached Property
private static void SetPageUpCanExecuteMonitor(DependencyObject obj, PageUpCanExecuteMonitor value)
{
obj.SetValue(PageUpCanExecuteMonitorProperty, value);
}
private static readonly DependencyProperty PageUpCanExecuteMonitorProperty =
DependencyProperty.RegisterAttached("PageUpCanExecuteMonitor", typeof (PageUpCanExecuteMonitor), typeof (ScrollBarCommandsCanExecuteFixBehavior), new PropertyMetadata(null));
#endregion
#region PageDownCanExecuteMonitor Attached Property
private static void SetPageDownCanExecuteMonitor(DependencyObject obj, PageDownCanExecuteMonitor value)
{
obj.SetValue(PageDownCanExecuteMonitorProperty, value);
}
private static readonly DependencyProperty PageDownCanExecuteMonitorProperty =
DependencyProperty.RegisterAttached("PageDownCanExecuteMonitor", typeof (PageDownCanExecuteMonitor), typeof (ScrollBarCommandsCanExecuteFixBehavior), new PropertyMetadata(null));
#endregion
}
The basic idea is that we add a CommandBinding to the ScrollViewer for each of the commands and subscribe to the CanExecute event on those bindings. In the event handler we check the current position of the scroll and set the e.CanExecute property accordingly.

WPF Watermark PasswordBox from Watermark TextBox

I am using a Watermark textbox as in Watermark TextBox in WPF
<Grid Grid.Row="0" Background="{StaticResource brushWatermarkBackground}" Style="{StaticResource EntryFieldStyle}" >
<TextBlock Margin="5,2" Text="This prompt dissappears as you type..." Foreground="{StaticResource brushWatermarkForeground}"
Visibility="{Binding ElementName=txtUserEntry, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBox Name="txtUserEntry" Background="Transparent" BorderBrush="{StaticResource brushWatermarkBorder}" />
</Grid>
How can I apply this for a PasswordBox?
The general approach is the same: write a custom control style, and show the watermark whenever the password box is empty. The only problem here is that PasswordBox.Password is not a dependency property, and you can't use it in a trigger. Also PasswordBox is sealed, so you can't override this notification behavior, but you can use attached properties here.
The following code demonstrates how.
XAML:
<Window x:Class="WpfTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfTest="clr-namespace:WpfTest"
Title="Password Box Sample" Height="300" Width="300">
<Window.Resources>
<Style x:Key="{x:Type PasswordBox}"
TargetType="{x:Type PasswordBox}">
<Setter Property="WpfTest:PasswordBoxMonitor.IsMonitoring"
Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type PasswordBox}">
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
SnapsToDevicePixels="true">
<Grid>
<ScrollViewer x:Name="PART_ContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<TextBlock Text="Please enter your password"
Margin="4, 2, 0, 0"
Foreground="Gray"
Visibility="Collapsed"
Name="txtPrompt" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled"
Value="false">
<Setter TargetName="Bd"
Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="WpfTest:PasswordBoxMonitor.PasswordLength" Value="0">
<Setter Property="Visibility" TargetName="txtPrompt" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<PasswordBox VerticalAlignment="Top"/>
</Grid>
</Window>
C#:
using System.Windows;
using System.Windows.Controls;
namespace WpfTest {
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
}
}
public class PasswordBoxMonitor : DependencyObject {
public static bool GetIsMonitoring(DependencyObject obj) {
return (bool)obj.GetValue(IsMonitoringProperty);
}
public static void SetIsMonitoring(DependencyObject obj, bool value) {
obj.SetValue(IsMonitoringProperty, value);
}
public static readonly DependencyProperty IsMonitoringProperty =
DependencyProperty.RegisterAttached("IsMonitoring", typeof(bool), typeof(PasswordBoxMonitor), new UIPropertyMetadata(false, OnIsMonitoringChanged));
public static int GetPasswordLength(DependencyObject obj) {
return (int)obj.GetValue(PasswordLengthProperty);
}
public static void SetPasswordLength(DependencyObject obj, int value) {
obj.SetValue(PasswordLengthProperty, value);
}
public static readonly DependencyProperty PasswordLengthProperty =
DependencyProperty.RegisterAttached("PasswordLength", typeof(int), typeof(PasswordBoxMonitor), new UIPropertyMetadata(0));
private static void OnIsMonitoringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var pb = d as PasswordBox;
if (pb == null) {
return;
}
if ((bool) e.NewValue) {
pb.PasswordChanged += PasswordChanged;
} else {
pb.PasswordChanged -= PasswordChanged;
}
}
static void PasswordChanged(object sender, RoutedEventArgs e) {
var pb = sender as PasswordBox;
if (pb == null) {
return;
}
SetPasswordLength(pb, pb.Password.Length);
}
}
}
Please notice PasswordBoxMonitor in XAML code.
you can show/hide the background by yourself instead of using triggers:
XAML:
<PasswordBox x:Name="passwordBox" PasswordChanged="passwordChanged"
Background="{StaticResource PasswordHint}" />
Code behind:
// helper to hide watermark hint in password field
private void passwordChanged(object sender, RoutedEventArgs e)
{
if (passwordBox.Password.Length == 0)
passwordBox.Background.Opacity = 1;
else
passwordBox.Background.Opacity = 0;
}
you can use my approach for a watermark behavior. all you have to do is copy and paste the TextBoxWatermarkBehavior and the change the Behavior<TextBox> to Behavior<PasswordBox>.
you can find a demo project here
#blindmeis's suggestion is good. For PasswordBox the class would be as follows.
public class PasswordBoxWatermarkBehavior : System.Windows.Interactivity.Behavior<PasswordBox>
{
private TextBlockAdorner adorner;
private WeakPropertyChangeNotifier notifier;
#region DependencyProperty's
public static readonly DependencyProperty LabelProperty =
DependencyProperty.RegisterAttached("Label", typeof(string), typeof(PasswordBoxWatermarkBehavior));
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelStyleProperty =
DependencyProperty.RegisterAttached("LabelStyle", typeof(Style), typeof(PasswordBoxWatermarkBehavior));
public Style LabelStyle
{
get { return (Style)GetValue(LabelStyleProperty); }
set { SetValue(LabelStyleProperty, value); }
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += this.AssociatedObjectLoaded;
this.AssociatedObject.PasswordChanged += AssociatedObjectPasswordChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.Loaded -= this.AssociatedObjectLoaded;
this.AssociatedObject.PasswordChanged -= this.AssociatedObjectPasswordChanged;
this.notifier = null;
}
private void AssociatedObjectPasswordChanged(object sender, RoutedEventArgs e)
{
this.UpdateAdorner();
}
private void AssociatedObjectLoaded(object sender, System.Windows.RoutedEventArgs e)
{
this.adorner = new TextBlockAdorner(this.AssociatedObject, this.Label, this.LabelStyle);
this.UpdateAdorner();
//AddValueChanged for IsFocused in a weak manner
this.notifier = new WeakPropertyChangeNotifier(this.AssociatedObject, UIElement.IsFocusedProperty);
this.notifier.ValueChanged += new EventHandler(this.UpdateAdorner);
}
private void UpdateAdorner(object sender, EventArgs e)
{
this.UpdateAdorner();
}
private void UpdateAdorner()
{
if (!String.IsNullOrEmpty(this.AssociatedObject.Password) || this.AssociatedObject.IsFocused)
{
// Hide the Watermark Label if the adorner layer is visible
this.AssociatedObject.TryRemoveAdorners<TextBlockAdorner>();
}
else
{
// Show the Watermark Label if the adorner layer is visible
this.AssociatedObject.TryAddAdorner<TextBlockAdorner>(adorner);
}
}
}

WPF Adding a Tooltip to the Track of a Slider

I have added a tooltip (shown below) to the track in the slider template, but rather than binding to the current value of the slider, I would like to bind to the value corresponding to the "track value" the mouse is over. Similar to what the youtube video slider allows. So the user can mouseover the track and also see the corresponding value, without having to actually move the thumb.
<Track Grid.Row="1" Name="PART_Track" ToolTip="{Binding Path=Value}" ToolTipService.Placement="Mouse">
</Track>
Any ideas? Thanks!
I have created an attached behaviour that will find the Track from a slider and subscribe to its MouseMove event to set the tooltip of the track to the corresponding value of the tick the mouse is over. I also added a Prefix property so you can write what the value is:
internal class ShowTickValueBehavior : Behavior<Slider>
{
private Track track;
public static readonly DependencyProperty PrefixProperty = DependencyProperty.Register(
"Prefix",
typeof(string),
typeof(ShowTickValueBehavior),
new PropertyMetadata(default(string)));
public string Prefix
{
get
{
return (string)this.GetValue(PrefixProperty);
}
set
{
this.SetValue(PrefixProperty, value);
}
}
protected override void OnAttached()
{
this.AssociatedObject.Loaded += this.AssociatedObjectOnLoaded;
base.OnAttached();
}
protected override void OnDetaching()
{
this.track.MouseMove -= this.TrackOnMouseMove;
this.track = null;
base.OnDetaching();
}
private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
this.AssociatedObject.Loaded -= this.AssociatedObjectOnLoaded;
this.track = (Track)this.AssociatedObject.Template.FindName("PART_Track", this.AssociatedObject);
this.track.MouseMove += this.TrackOnMouseMove;
}
private void TrackOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var position = mouseEventArgs.GetPosition(this.track);
var valueFromPoint = this.track.ValueFromPoint(position);
var floorOfValueFromPoint = (int)Math.Floor(valueFromPoint);
var toolTip = string.Format(CultureInfo.InvariantCulture, "{0}{1}", this.Prefix, floorOfValueFromPoint);
ToolTipService.SetToolTip(this.track, toolTip);
}
}
Usage
<Window x:Class="TestSlider.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-TestSlider"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
>
<Grid>
<Slider Name="Slider1"
IsSnapToTickEnabled="True"
TickFrequency="1"
TickPlacement="BottomRight"
IsMoveToPointEnabled="True"
Minimum="13"
Maximum="25"
>
<i:Interaction.Behaviors>
<local:ShowTickValueBehavior Prefix="Volume: "/>
</i:Interaction.Behaviors>
</Slider>
</Grid>
Result:
I'd imagine you're going to have to create a new control, inheriting from the slider. You'd need to implement mousein/out and mousemove, calculate the value based on mouse offset and change the tooltip.
I don't think there's any property you can use "out of the box" so the calculation may be rather tricky if you need to factor in reskinning of margins etc.
First you need modify Slider control
<Slider VerticalAlignment="Center" HorizontalAlignment="Stretch">
<Slider.Template>
<ControlTemplate TargetType="{x:Type Slider}">
<Grid>
<xamlTest:ReflectablePopup x:Name="InfoPopup" Width="Auto" Height="Auto" PlacementTarget="{Binding ElementName=Thumb}" Placement="Top" StaysOpen="False" IsOpen="False" AllowsTransparency="True">
<Border Padding="2" CornerRadius="3" Background="#555C5C5C">
<Label Content="Your Text"></Label>
</Border>
</xamlTest:ReflectablePopup>
<Track x:Name="PART_Track">
<Track.Thumb>
<Thumb x:Name="Thumb" Width="10" Height="20">
</Thumb>
</Track.Thumb>
</Track>
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="Thumb" Property="IsDragging" Value="True">
<Setter Value="True" TargetName="InfoPopup" Property="IsOpen" />
</Trigger>
<Trigger SourceName="Thumb" Property="IsDragging" Value="False">
<Setter Value="False" TargetName="InfoPopup" Property="IsOpen" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Slider.Template>
</Slider>
As you see, I using ReflectablePopup instead Popup. Because Popup can`t relocate after PlacementTarget moved.
Below the code fo ReflectablePopup (C#):
public class ReflectablePopup : Popup
{
protected override void OnOpened(EventArgs e)
{
var friend = this.PlacementTarget;
friend.QueryCursor += friend_QueryCursor;
base.OnOpened(e);
}
protected override void OnClosed(EventArgs e)
{
var friend = this.PlacementTarget;
friend.QueryCursor -= friend_QueryCursor;
base.OnClosed(e);
}
private void friend_QueryCursor(object sender, System.Windows.Input.QueryCursorEventArgs e)
{
this.HorizontalOffset += +0.1;
this.HorizontalOffset += -0.1;
}
}

Resources