I'm trying to build Inventory like interface for project that I'm creating.
Idea is to have list of images that can be dragged to players as shown below:
Images are loaded from directory and displayed inside ListView, list of players is displayed in ListBox.
My XAML looks like this:
<Window x:Class="DynamicImagesDrag.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dynamicImagesDrag="clr-namespace:DynamicImagesDrag"
Title="MainWindow"
Height="405"
Width="719.162"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<dynamicImagesDrag:StringToImageConverter x:Key="StringToImageConverter" />
</Window.Resources>
<Grid>
<ListView Name="MyList"
ItemsSource="{Binding Images}"
PreviewMouseLeftButtonDown="UIElement_OnPreviewMouseLeftButtonDown"
PreviewMouseMove="UIElement_OnPreviewMouseMove"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
VerticalAlignment="Stretch"
HorizontalAlignment="Left"
Margin="10" Width="250">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel Width="50" Height="50">
<DockPanel.Background>
<ImageBrush ImageSource="BG1.png"/>
</DockPanel.Background>
<Image Source="{Binding Path, Converter={StaticResource StringToImageConverter} }"
Height="32"
Width="32" />
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListBox ItemsSource="{Binding People}" HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Margin="10" VerticalAlignment="Stretch"
Width="200">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" AllowDrop="True" PreviewDrop="UIElement_OnPreviewDrop">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<ProgressBar Height="20" Value="{Binding Points}" Margin="0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code behind:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace DynamicImagesDrag
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
private readonly ObservableCollection<MyImage> _images = new ObservableCollection<MyImage>();
public ObservableCollection<MyImage> Images
{
get { return _images; }
}
private readonly ObservableCollection<Person> _people = new ObservableCollection<Person>();
public ObservableCollection<Person> People { get { return _people; } }
public MainWindow()
{
InitializeComponent();
_people.Add(new Person() { Name = "Person1", Points = 10 });
_people.Add(new Person() { Name = "Person2", Points = 0 });
_people.Add(new Person() { Name = "Person3", Points = 40 });
string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (appPath != null)
{
string imagePath = Path.Combine(appPath, "Images");
if (Directory.Exists(imagePath))
{
var images = Directory
.EnumerateFiles(imagePath)
.Where(file => file.ToLower().EndsWith("jpg") || file.ToLower().EndsWith("png"))
.ToList();
foreach (string image in images)
{
_images.Add(new MyImage
{
Name = Path.GetFileName(image),
Path = image,
Points = Convert.ToInt32(Path.GetFileNameWithoutExtension(image))
});
}
}
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
public static Point GetMousePosition()
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
private void UIElement_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_startPoint = e.GetPosition(null);
}
#region Field and Properties
private bool _dragHasLeftScope;
private Point _startPoint;
public bool IsDragging { get; set; }
DragAdorner _adorner;
AdornerLayer _layer;
public FrameworkElement DragScope { get; set; }
#endregion // Field and Properties
private void UIElement_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
// Ensure that the user does not drag by accident
if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
{
Point position = e.GetPosition(null);
if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
{
StartDragInProcAdorner(e);
}
}
}
void DragScope_DragLeave(object sender, DragEventArgs e)
{
if (e.OriginalSource == DragScope)
{
Point p = e.GetPosition(DragScope);
Rect r = VisualTreeHelper.GetContentBounds(DragScope);
if (!r.Contains(p))
{
_dragHasLeftScope = true;
e.Handled = true;
}
}
}
void Window1_DragOver(object sender, DragEventArgs args)
{
if (_adorner == null) return;
_adorner.LeftOffset = args.GetPosition(DragScope).X /* - _startPoint.X */ ;
_adorner.TopOffset = args.GetPosition(DragScope).Y /* - _startPoint.Y */ ;
}
void DragScope_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (_dragHasLeftScope)
{
e.Action = DragAction.Cancel;
e.Handled = true;
}
}
private void StartDragInProcAdorner(MouseEventArgs e)
{
DragScope = Application.Current.MainWindow.Content as FrameworkElement;
bool previousDrop = DragScope.AllowDrop;
DragScope.AllowDrop = true;
try
{
DragEventHandler draghandler = Window1_DragOver;
DragScope.PreviewDragOver += draghandler;
DragEventHandler dragleavehandler = DragScope_DragLeave;
DragScope.DragLeave += dragleavehandler;
QueryContinueDragEventHandler queryhandler = DragScope_QueryContinueDrag;
DragScope.QueryContinueDrag += queryhandler;
DragScope.GiveFeedback+=DragScope_GiveFeedback;
FrameworkElement dr = MyList.ItemContainerGenerator.ContainerFromItem(MyList.SelectedItem) as FrameworkElement;
if (dr == null)
return;
_adorner = new DragAdorner(DragScope, dr, true, 0.5);
_layer = AdornerLayer.GetAdornerLayer(DragScope);
_layer.Add(_adorner);
IsDragging = true;
_dragHasLeftScope = false;
DataObject data = new DataObject(MyList.SelectedItem as MyImage);
DragDropEffects de = DragDrop.DoDragDrop(MyList, data, DragDropEffects.Move);
DragScope.AllowDrop = previousDrop;
AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
_adorner = null;
DragScope.DragLeave -= dragleavehandler;
DragScope.QueryContinueDrag -= queryhandler;
DragScope.PreviewDragOver -= draghandler;
IsDragging = false;
}
catch
{
DragScope.AllowDrop = previousDrop;
AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
_adorner = null;
IsDragging = false;
}
}
private void DragScope_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
}
private void UIElement_OnPreviewDrop(object sender, DragEventArgs e)
{
var stackPanel = sender as StackPanel;
if (stackPanel == null) return;
var student = stackPanel.DataContext as Person;
MyImage myImage = e.Data.GetData(typeof(MyImage)) as MyImage;
if (student != null) student.Points += myImage.Points;
}
}
public class StringToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
return new BitmapImage(new Uri((string)value));
}
catch
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class Person : INotifyPropertyChanged
{
private string _name;
private int _points;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public int Points
{
get { return _points; }
set
{
if (value == _points) return;
_points = value;
if (_points >= 100)
{
_points -= 100;
Debug.WriteLine("100!");
}
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MyImage
{
public string Path { get; set; }
public string Name { get; set; }
public int Points { get; set; }
}
}
and DragAdorner (taken from http://www.infragistics.com/community/blogs/alex_fidanov/archive/2009/07/28/drag-amp-drop-with-datapresenter-family-controls.aspx)
class DragAdorner : Adorner
{
public DragAdorner(UIElement owner) : base(owner) { }
public DragAdorner(UIElement owner, UIElement adornElement, bool useVisualBrush, double opacity)
: base(owner)
{
_owner = owner;
VisualBrush _brush = new VisualBrush
{
Opacity = opacity,
Visual = adornElement
};
DropShadowEffect dropShadowEffect = new DropShadowEffect
{
Color = Colors.Black,
BlurRadius = 15,
Opacity = opacity
};
Rectangle r = new Rectangle
{
RadiusX = 3,
RadiusY = 3,
Fill = _brush,
Effect = dropShadowEffect,
Width = adornElement.DesiredSize.Width,
Height = adornElement.DesiredSize.Height
};
XCenter = adornElement.DesiredSize.Width / 2;
YCenter = adornElement.DesiredSize.Height / 2;
_child = r;
}
private void UpdatePosition()
{
AdornerLayer adorner = (AdornerLayer)Parent;
if (adorner != null)
{
adorner.Update(AdornedElement);
}
}
#region Overrides
protected override Visual GetVisualChild(int index)
{
return _child;
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Size MeasureOverride(Size finalSize)
{
_child.Measure(finalSize);
return _child.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
_child.Arrange(new Rect(_child.DesiredSize));
return finalSize;
}
public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
{
GeneralTransformGroup result = new GeneralTransformGroup();
result.Children.Add(base.GetDesiredTransform(transform));
result.Children.Add(new TranslateTransform(_leftOffset, _topOffset));
return result;
}
#endregion
#region Field & Properties
public double scale = 1.0;
protected UIElement _child;
protected VisualBrush _brush;
protected UIElement _owner;
protected double XCenter;
protected double YCenter;
private double _leftOffset;
public double LeftOffset
{
get { return _leftOffset; }
set
{
_leftOffset = value - XCenter;
UpdatePosition();
}
}
private double _topOffset;
public double TopOffset
{
get { return _topOffset; }
set
{
_topOffset = value - YCenter;
UpdatePosition();
}
}
#endregion
}
Drag works almost fine:
except adorner is visible only in source list and target list, it isn't displayed during whole drag.
My questions are:
How can I fix drag and drop to see adorner whole time?
How can I display image instead selectedItem inside adorner? Right now inside adorner is that brown background, I'd like to get only transparent image.
How can I show if dragtarget is correct inside adorner instead of changing cursor? I'd like to change opacity of adorner if target is correct.
I'd like to get drag and drop working with touch events, #KOTIX suggested using Gong WPF dragdrop, will it work fine on touch enabled screens?
Currently I'm setting AllowDrop on StackPanel inside ListBox ItemTemplate, should it stay there or maybe I should set in on ListBox?
I've searched over internet (including SO) for solution, but I couldn't find anything that fits my needs.
I found some great articles:
http://www.codeproject.com/Articles/37161/WPF-Drag-and-Drop-Smorgasbord
http://www.zagstudio.com/blog/488#.VgHPyxHtmkp
http://nonocast.cn/adorner-in-wpf-part-5-drag-and-drop/
https://blogs.claritycon.com/blog/2009/03/generic-wpf-drag-and-drop-adorner/
Last one was very interesting, but I wasn't able to modify it in a way to add points to players instead of moving items. In my case I want items on left to stay, I just want to update list on right based on dragged item.
I don't have all the answers yet but it may lead you to your goals.
How can I fix drag and drop to see adorner whole time?
Your DragLayer is Transparent, just set a value for the Background property of your Grid
How can I display image instead selectedItem inside adorner? Right now
inside adorner is that brown background, I'd like to get only
transparent image.
The best I've come to is to explore the VisualTree to get the Image inside the template :
//Get the `ListViewItem`
FrameworkElement dr = MyList.ItemContainerGenerator.ContainerFromItem(MyList.SelectedItem) as FrameworkElement;
//Explore the VisualTree to get the grand-child
//This should be refactored to a Func<UIElement,UIElement> to accord to templates changes
UIElement el = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(dr, 0), 0) as UIElement;
//Create the DragAdorner using the found UIElement
_adorner = new DragAdorner(DragScope, el, true, 1d);
How can I show if dragtarget is correct inside adorner instead of
changing cursor? I'd like to change opacity of adorner if target is
correct.
To show some feedback, you need to... GiveFeedBack
Here is your handler :
private void DragScope_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
if (_adorner == null) return;
if (e.Effects == DragDropEffects.Copy)
{
_adorner.Opacity = 1d;
e.Handled = true;
}
else
{
_adorner.Opacity = 0.5d;
e.Handled = true;
}
}
Now, you have to set the desired effect in 2 places : the Person template and the DragLayer :
private void StackPanel_DragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.Copy;
e.Handled = true;
}
void Window1_DragOver(object sender, DragEventArgs args)
{
if (_adorner == null) return;
_adorner.LeftOffset = args.GetPosition(DragScope).X;
_adorner.TopOffset = args.GetPosition(DragScope).Y;
if (!args.Handled)
args.Effects = DragDropEffects.Move;
}
In order for these to work, you have to allow those 2 effects when initiating the DragDrop :
DragDropEffects de = DragDrop.DoDragDrop(MyList, data, DragDropEffects.Move | DragDropEffects.Copy);
Also, to avoid cumulative opacity reduction, use 1 for the DragAdorner opacity in constructor.
I'd like to get drag and drop working with touch events, #KOTIX
suggested using Gong WPF dragdrop, will it work fine on touch enabled
screens?
This solution is fully native, you should get it to work with touch devices.
Currently I'm setting AllowDrop on StackPanel inside ListBox
ItemTemplate, should it stay there or maybe I should set in on
ListBox?
If your goal is to add MyImage.Points to the drop target, the AllowDrop must be set on the StackPanel.
I would suggest using Gong WPF dragdrop
This library always worked great for me and way better than built-in support. It also adds great support for MVVM design.
Good luck!
I am working on a simple Custom Control that should go to Edit mode by double clicking on it
The concept is based on this question Click-to-edit in Silverlight
On a double click it changes initial template on Edit Template
and it seems to be pretty clear, except the part (5) How to change the template Back when the Control Looses the focus
The Lost Focus event is fired only when contained controls are loosing focus
Here is an article that talk about it
http://programmerpayback.com/2008/11/20/gotfocus-and-lostfocus-events-on-containers/
I have tried to implement same Technic but still no result, I cannot get LostFocus event working for me when I click outside of a control
Where is my issue?
My XAML
<ContentControl x:Class="Splan_RiaBusinessApplication.Controls.TimeCodeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:obj="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:behaviour="clr-namespace:Splan_RiaBusinessApplication.Behavior"
xmlns:controls="clr-namespace:Splan_RiaBusinessApplication.Controls"
xmlns:Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
IsTabStop="True"
IsEnabled="True"
Visibility="Visible"
d:DesignHeight="100" d:DesignWidth="200"
d:Height="200" d:Width="200"
>
<ContentControl.Resources>
<ControlTemplate x:Key="DisplayTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Target.Code, Mode=TwoWay}" />
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" >
<TextBlock Text="{Binding Target.Start, Mode=TwoWay, StringFormat=hh\\:mm }" />
<TextBlock Text='-' />
<TextBlock Text="{Binding Target.End, Mode=TwoWay, StringFormat=hh\\:mm }" />
</StackPanel>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="EditTemplate">
<Grid Background="Aqua" Height="200" Width="200">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox Width="100" Height="25" x:Name="cbTimeCode"
ItemsSource="{Binding TimeCodes}"
SelectedValue="{Binding Target.CodeId, Mode=TwoWay}"
SelectedValuePath="TimeCodeId"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Target.Code}" />
<TextBlock Grid.Column="1" Text="{Binding Target.Description}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger>
<behaviour:ResolveElementName PropertyName="ItemsSource" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<!--<controls:TimeRangePickerControl Grid.Row="1" StartTime="{Binding Target.Start, Mode=TwoWay}" EndTime="{Binding Target.End, Mode=TwoWay}"/>-->
</Grid>
</ControlTemplate>
</ContentControl.Resources>
<Grid x:Name="Layout" Background="Aquamarine">
<ItemsControl x:Name="PlaceHolder" Template="{StaticResource DisplayTemplate}">
</ItemsControl>
</Grid>
</ContentControl>
Code Behind
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Splan_RiaBusinessApplication.Controls
{
public class TimeCode
{
public int TimeCodeId {get;set;}
public string Code { get; set; }
public string Description { get; set; }
}
public class TimeDetail
{
public int TimeDetailId { get;set; }
public int CodeId { get;set;}
public TimeSpan Start { get; set; }
public TimeSpan End { get; set; }
public string Code { get; set; }
public string Comment { get; set; }
}
public partial class TimeCodeControl : ContentControl
{
public class TimeCodeControlEventArgs : EventArgs
{
public string userName { get; set; }
}
private static TimeSpan DoubleClickThreshold = TimeSpan.FromMilliseconds(300);
private DateTime _lastClick;
private Boolean m_EditMode = false;
public Boolean EditMode
{
get { return m_EditMode; }
set
{
if (m_EditMode != value)
{
switch (value)
{
case false:
PlaceHolder.Template = this.Resources["DisplayTemplate"] as ControlTemplate;
break;
case true:
PlaceHolder.Template = this.Resources["EditTemplate"] as ControlTemplate;
break;
}
m_EditMode = value;
}
}
}
public bool IsFocused
{
get
{
return FocusManager.GetFocusedElement() == this;
}
}
public TimeCodeControl()
{
TimeCodes = new List<TimeCode>() { new TimeCode { TimeCodeId = 200, Code= "C", Description="Cash" } };
InitializeComponent();
Layout.DataContext = this;
this.IsTabStop = true;
this.Visibility = Visibility.Visible;
this.IsEnabled = true;
this.Focus();
Layout.MouseLeftButtonDown += Layout_MouseLeftButtonDown;
//Layout.KeyDown += Layout_KeyDown;
//Layout.KeyUp += Layout_KeyUp;
this.LostFocus += TimeCodeControl_LostFocus;
this.GotFocus += TimeCodeControl_GotFocus;
}
void TimeCodeControl_GotFocus(object sender, RoutedEventArgs e)
{
}
void TimeCodeControl_LostFocus(object sender, RoutedEventArgs e)
{
}
public TimeDetail Source
{
get { return (TimeDetail)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(TimeDetail), typeof(TimeCodeControl),
new PropertyMetadata(SourceChanged));
private static void SourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as TimeCodeControl;
if (control == null) return;
control.Target = control.Source; //.Copy();
}
public List<TimeCode> TimeCodes { get; set; }
public TimeDetail Target { get; set; }
private bool FocusIsInside(object parent)
{
bool rs = false;
dynamic oFocus = FocusManager.GetFocusedElement();
while (oFocus != null)
try
{
if ((oFocus.GetType() == parent.GetType()) && (oFocus.Equals(this)))
{
rs = true;
break;
}
else
{
oFocus = oFocus.Parent;
}
}
catch
{
break;
}
return rs;
}
private Boolean hasFocus = false;
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (!hasFocus)
{
hasFocus = true;
Debug.WriteLine("Container Got Focus");
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
//if (!FocusIsInside(Layout))
//{
// hasFocus = false;
// Debug.WriteLine("Container Lost Focus");
// EditMode = false;
//}
}
void Layout_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (DateTime.Now - this._lastClick <= DoubleClickThreshold)
{
EditMode = true;
this._lastClick = DateTime.Now;
e.Handled = true;
return;
}
this._lastClick = DateTime.Now;
}
}
}
UPDATE :
I decided to utilize timers to identify a scenario when user brings a focus from outside of the container or just switches focus from one control to another inside of the container. it may be not the best solution but it seems to be working for now. I would appreciate any suggestions or recommendations on different approaches or implementations.
public partial class MyControl: ContentControl
{
...
public event EventHandler<RoutedEventArgs> LostFocus;
public event EventHandler<RoutedEventArgs> GotFocus;
bool Focused = false;
DispatcherTimer FocusTimer = null;
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (Focused) return;
Focused = true;
// it focused from the outside of the control
// becouse the timer wasn't initialised on the previous LostFocused event
// generated by other control in the same ContentControl contaner
if (FocusTimer == null)
{
if (GotFocus != null)
GotFocus(e.OriginalSource, e);
Debug.WriteLine("Got Focus ");
return;
}
// It was switched from one hosted control to another one
FocusTimer.Stop();
FocusTimer = null;
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (e.OriginalSource is ComboBox && FocusManager.GetFocusedElement() is ComboBoxItem)
return;
FocusTimer = new DispatcherTimer();
Focused = false;
FocusTimer.Interval = new TimeSpan(0, 0, 0, 0, 50);
FocusTimer.Tick += (s, args) =>
{
FocusTimer.Stop();
FocusTimer = null;
// if after the timeout the focus still not triggered
// by another contained element
// the We lost a focus on the container
if (!Focused )
{
if(LostFocus != null)
LostFocus(e.OriginalSource, e);
Debug.WriteLine("Lost Focus " );
}
};
FocusTimer.Start();
}
...
}
There are several issues. Let's see...
Why are you not getting a LostFocus event when you click outside of the control?
Well, I fell victim to this false assumption some time ago too. The click outside does not change the focus unless you click a control that explicitly sets focus to itself on click (like a TextBox does, or the various Buttons).
Press Tab to navigate the keyboard focus to the next control and see if the event is raised.
But let's talk about the other issues:
ControlTemplate x:Key="DisplayTemplate" and ControlTemplate x:Key="EditTemplate"
Using ControlTemplates this way is not recommended. Rather use DataTemplate and corresponding ContentPresenters.
TimeCodeControl : ContentControl and x:Class="Splan_RiaBusinessApplication.Controls.TimeCodeControl"
Yes I know that's possible, but not really useful. Let me explain:
You can write your own specialized Click-To-Edit Control as a one-shot tool: having a hardcoded DisplayTemplate and EditTemplate to edit TimeCode and TimeDetail data (basically what you did). But then you have no chance of ever using it and specifying another pair of Templates to allow editing of other data types.
So it doesn't make much sense to derive from ContentControl, you could as well derive from UserControl.
An alternative would be: Write your Click-To-Edit control as a general and reusable control, that offers two public properties: DisplayTemplate and EditTemplate. And don't make any assumptions about your DataContext. And again there is no benefit from having ContentControl as the parent class.
I recommend you derive from Control, add two DependencyProperties of type DataTemplate as mentioned earlier, define a default ControlTemplate with one or two ContentPresenters inside. In your control code you need to handle MouseLeftButtonDown and LostFocus and update a boolean flag accordingly.
Here is a working example:
...extension method to determine focus:
public static class ControlExtensions
{
public static bool IsFocused( this UIElement control )
{
DependencyObject parent;
for (DependencyObject potentialSubControl = FocusManager.GetFocusedElement() as DependencyObject; potentialSubControl != null; potentialSubControl = parent)
{
if (object.ReferenceEquals( potentialSubControl, control ))
{
return true;
}
parent = VisualTreeHelper.GetParent( potentialSubControl );
if (parent == null)
{
FrameworkElement element = potentialSubControl as FrameworkElement;
if (element != null)
{
parent = element.Parent;
}
}
}
return false;
}
}
...and a nice custom control:
public class ClickToEditControl : Control
{
public ClickToEditControl()
{
DefaultStyleKey = typeof (ClickToEditControl);
MouseLeftButtonDown += OnMouseLeftButtonDown;
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount==2)
{
GotoEditMode();
e.Handled = true;
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (!this.IsFocused())
GotoDisplayMode();
}
private void GotoDisplayMode()
{
IsInEditMode = false;
}
private void GotoEditMode()
{
IsInEditMode = true;
}
public DataTemplate EditTemplate
{
get { return (DataTemplate) GetValue( EditTemplateProperty ); }
set { SetValue( EditTemplateProperty, value ); }
}
public static readonly DependencyProperty EditTemplateProperty =
DependencyProperty.Register( "EditTemplate", typeof( DataTemplate ), typeof( ClickToEditControl ), null );
public DataTemplate DisplayTemplate
{
get { return (DataTemplate) GetValue( DisplayTemplateProperty ); }
set { SetValue( DisplayTemplateProperty, value ); }
}
public static readonly DependencyProperty DisplayTemplateProperty =
DependencyProperty.Register( "DisplayTemplate", typeof( DataTemplate ), typeof( ClickToEditControl ), null );
public bool IsInEditMode
{
get { return (bool) GetValue( IsInEditModeProperty ); }
set { SetValue( IsInEditModeProperty, value ); }
}
public static readonly DependencyProperty IsInEditModeProperty =
DependencyProperty.Register( "IsInEditMode", typeof( bool ), typeof( ClickToEditControl ), null );
}
...and ControlTemplate:
<clickToEdit:BoolToVisibilityConverter x:Key="VisibleIfInEditMode"/>
<clickToEdit:BoolToVisibilityConverter x:Key="CollapsedIfInEditMode" VisibleIfTrue="False"/>
<Style TargetType="clickToEdit:ClickToEditControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="clickToEdit:ClickToEditControl">
<Grid>
<ContentPresenter
ContentTemplate="{TemplateBinding EditTemplate}"
Content="{Binding}"
Visibility="{Binding IsInEditMode, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource VisibleIfInEditMode}}"/>
<ContentPresenter
ContentTemplate="{TemplateBinding DisplayTemplate}"
Content="{Binding}"
Visibility="{Binding IsInEditMode, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource CollapsedIfInEditMode}}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
BoolToVisibilityConverter
public class BoolToVisibilityConverter : IValueConverter
{
public bool VisibleIfTrue { get; set; }
public BoolToVisibilityConverter(){VisibleIfTrue = true;}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (VisibleIfTrue)
return ((bool) value) ? Visibility.Visible : Visibility.Collapsed;
else
return ((bool) value) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotSupportedException();}
}
Usage:
<clickToEdit:ClickToEditControl Height="20" Width="200">
<clickToEdit:ClickToEditControl.DisplayTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyText}"/>
</DataTemplate>
</clickToEdit:ClickToEditControl.DisplayTemplate>
<clickToEdit:ClickToEditControl.EditTemplate>
<DataTemplate>
<TextBox Text="{Binding MyText, Mode=TwoWay}"/>
</DataTemplate>
</clickToEdit:ClickToEditControl.EditTemplate>
</clickToEdit:ClickToEditControl>
So I'm trying to build a drop down color picker custom control. I currently have it set so when I click the rectangle part the popup part opens. I would like to have it so when I click anywhere outside the control the popup closes. I wrote a MouseButtonEventHandler and registered it with Mouse.AddPreviewMouseDownOutsideCapturedElementHandler. However, the handler never hits no matter where I click in the window. I find it odd because there's two controls in the WPF Toolkit that utilize this method. The two being the DropDownButton Control and the CalculatorUpDown Control. Am I missing anything that would prevent the handler from firing?
Control Code:
[TemplatePart(Name = DisplayColorPart, Type = typeof(Rectangle))]
[TemplatePart(Name = DropDownPart, Type = typeof(Popup))]
public class ColorPickerDropDown : Control
{
private const string DisplayColorPart = "PART_DisplayColor";
private const string DropDownPart = "PART_DropDown";
private Rectangle _displayColorElement;
private Popup _dropDownElement;
public Rectangle DisplayColorElement
{
get { return _displayColorElement; }
set
{
_displayColorElement = value;
InitalizeDisplayColorElement();
}
}
public Popup DropDownElement
{
get { return _dropDownElement; }
set { _dropDownElement = value; }
}
public static readonly DependencyProperty CurrentColorProperty =
DependencyProperty.Register("CurrentColor", typeof(Brush), typeof(ColorPickerDropDown),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public Brush CurrentColor
{
get { return (Brush)GetValue(CurrentColorProperty); }
set { SetValue(CurrentColorProperty, value); }
}
static ColorPickerDropDown()
{
DefaultStyleKeyProperty
.OverrideMetadata(typeof (ColorPickerDropDown),
new FrameworkPropertyMetadata(typeof (ColorPickerDropDown)));
}
public ColorPickerDropDown()
{
Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OutsideControlClick);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (DisplayColorElement == null) DisplayColorElement = GetTemplateChild(DisplayColorPart) as Rectangle;
if (DropDownElement == null) DropDownElement = GetTemplateChild(DropDownPart) as Popup;
}
private void InitalizeDisplayColorElement()
{
if (DisplayColorElement == null) return;
DisplayColorElement.AddHandler(Rectangle.MouseLeftButtonDownEvent,
new RoutedEventHandler(DisplayColorClick),
handledEventsToo: true);
}
private void DisplayColorClick(object sender, RoutedEventArgs e)
{
DropDownElement.IsOpen = DropDownElement.IsOpen ? false : true;
}
public void OutsideControlClick(object sender, MouseButtonEventArgs e)
{
if (!DropDownElement.IsMouseOver && !DisplayColorElement.IsMouseOver)
{
DropDownElement.IsOpen = false;
}
}
}
Control Style:
<Style TargetType="{x:Type local:ColorPickerDropDown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ColorPickerDropDown}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Background="Transparent">
<Rectangle Name="PART_DisplayColor"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
Fill="{Binding CurrentColor, RelativeSource={RelativeSource TemplatedParent}}"/>
<Popup Name="PART_DropDown"
PlacementTarget="{Binding ElementName=PART_DisplayColor}"
Width="{Binding ElementName=PART_DisplayColor}"
StaysOpen="True"
AllowsTransparency="True">
<Border Background="DodgerBlue"
BorderThickness="1"
BorderBrush="AliceBlue"
CornerRadius="0,0,0,15">
<StackPanel>
<Rectangle Height="100"
Width="98"/>
</StackPanel>
</Border>
</Popup>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The PreviewMouseDownOutsideCapturedElement attached event works only if you have enabled mouse capture using the Mouse.Capture() method. Therefore, you need to start mouse capture as soon as you open the popup. When mouse capture is active, IsMouseOver unfortunately cannot be used any longer; you have to check the mouse coordinates directly (see the method IsWithin in my sample below).
I have modified your code as follows (see places marked with ***) and it worked for me:
[TemplatePart(Name = DisplayColorPart, Type = typeof(Rectangle))]
[TemplatePart(Name = DropDownPart, Type = typeof(Popup))]
public class ColorPickerDropDown : Control
{
private const string DisplayColorPart = "PART_DisplayColor";
private const string DropDownPart = "PART_DropDown";
private Rectangle _displayColorElement;
private Popup _dropDownElement;
public Rectangle DisplayColorElement
{
get { return _displayColorElement; }
set
{
_displayColorElement = value;
InitalizeDisplayColorElement();
}
}
public Popup DropDownElement
{
get { return _dropDownElement; }
set { _dropDownElement = value; }
}
public static readonly DependencyProperty CurrentColorProperty =
DependencyProperty.Register("CurrentColor", typeof(Brush), typeof(ColorPickerDropDown),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public Brush CurrentColor
{
get { return (Brush)GetValue(CurrentColorProperty); }
set { SetValue(CurrentColorProperty, value); }
}
static ColorPickerDropDown()
{
DefaultStyleKeyProperty
.OverrideMetadata(typeof(ColorPickerDropDown),
new FrameworkPropertyMetadata(typeof(ColorPickerDropDown)));
}
public ColorPickerDropDown()
{
Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OutsideControlClick);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (DisplayColorElement == null) DisplayColorElement = GetTemplateChild(DisplayColorPart) as Rectangle;
if (DropDownElement == null) DropDownElement = GetTemplateChild(DropDownPart) as Popup;
}
private void InitalizeDisplayColorElement()
{
if (DisplayColorElement == null) return;
DisplayColorElement.AddHandler(Rectangle.MouseLeftButtonDownEvent,
new RoutedEventHandler(DisplayColorClick),
handledEventsToo: true);
}
private void DisplayColorClick(object sender, RoutedEventArgs e)
{
DropDownElement.IsOpen = DropDownElement.IsOpen ? false : true;
if (DropDownElement.IsOpen) // ***
{ // ***
Mouse.Capture(this); // ***
} // ***
}
public void OutsideControlClick(object sender, MouseButtonEventArgs e)
{
if (!IsWithin(DropDownElement.Child, e) && !IsWithin(DisplayColorElement, e)) // ***
{
DropDownElement.IsOpen = false;
Mouse.Capture(null); // ***
}
}
// *** Is mouse within the bounds of the UI element?
public static bool IsWithin(UIElement element, MouseButtonEventArgs e)
{
FrameworkElement elem = element as FrameworkElement;
return (elem == null) ? false :
new Rect(0, 0, elem.ActualWidth, elem.ActualHeight).Contains(e.GetPosition(element));
}
}
Please also note that there is a small typo in your XAML. The line
Width="{Binding ElementName=PART_DisplayColor}"
should be
Width="{Binding ElementName=PART_DisplayColor, Path=ActualWidth}"
I am looking for a way to add placeholder text to a textbox like you can with a textbox in html5.
I.e. if the textbox has no text, then it adds the text Enter some text here, when the user clicks on it the placeholder text disappears and allows the user to enter their own text, and if the textbox loses focus and there is still no text then the placeholder is added back to the textbox.
Wouldn't that just be something like this:
Textbox myTxtbx = new Textbox();
myTxtbx.Text = "Enter text here...";
myTxtbx.GotFocus += GotFocus.EventHandle(RemoveText);
myTxtbx.LostFocus += LostFocus.EventHandle(AddText);
public void RemoveText(object sender, EventArgs e)
{
if (myTxtbx.Text == "Enter text here...")
{
myTxtbx.Text = "";
}
}
public void AddText(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(myTxtbx.Text))
myTxtbx.Text = "Enter text here...";
}
Thats just pseudocode but the concept is there.
You can use this, it's working for me and is extremely simple solution.
<Style x:Key="placeHolder" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<TextBox Text="{Binding Path=Text,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
x:Name="textSource"
Background="Transparent"
Panel.ZIndex="2" />
<TextBox Text="{TemplateBinding Tag}" Background="{TemplateBinding Background}" Panel.ZIndex="1">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text, Source={x:Reference textSource}}" Value="">
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage:
<TextBox Style="{StaticResource placeHolder}" Tag="Name of customer" Width="150" Height="24"/>
Instead of handling the focus enter and focus leave events in order to set and remove the placeholder text it is possible to use the Windows SendMessage function to send EM_SETCUEBANNER message to our textbox to do the work for us.
This can be done with two easy steps. First we need to expose the Windows SendMessage function.
private const int EM_SETCUEBANNER = 0x1501;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern Int32 SendMessage(IntPtr hWnd, int msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)]string lParam);
Then simply call the method with the handle of our textbox, EM_SETCUEBANNER’s value and the text we want to set.
SendMessage(textBox1.Handle, EM_SETCUEBANNER, 0, "Username");
SendMessage(textBox2.Handle, EM_SETCUEBANNER, 0, "Password");
Reference: Set placeholder text for textbox (cue text)
Add this class your project and build your solution. Click to Toolbox on visual studio you will see a new textbox component named PlaceholderTextBox. Delete your current textbox on form designe and replace with PlaceHolderTextBox.
PlaceHolderTextBox has a property PlaceHolderText. Set any text you want and have nice day :)
public class PlaceHolderTextBox : TextBox
{
bool isPlaceHolder = true;
string _placeHolderText;
public string PlaceHolderText
{
get { return _placeHolderText; }
set
{
_placeHolderText = value;
setPlaceholder();
}
}
public new string Text
{
get => isPlaceHolder ? string.Empty : base.Text;
set => base.Text = value;
}
//when the control loses focus, the placeholder is shown
private void setPlaceholder()
{
if (string.IsNullOrEmpty(base.Text))
{
base.Text = PlaceHolderText;
this.ForeColor = Color.Gray;
this.Font = new Font(this.Font, FontStyle.Italic);
isPlaceHolder = true;
}
}
//when the control is focused, the placeholder is removed
private void removePlaceHolder()
{
if (isPlaceHolder)
{
base.Text = "";
this.ForeColor = System.Drawing.SystemColors.WindowText;
this.Font = new Font(this.Font, FontStyle.Regular);
isPlaceHolder = false;
}
}
public PlaceHolderTextBox()
{
GotFocus += removePlaceHolder;
LostFocus += setPlaceholder;
}
private void setPlaceholder(object sender, EventArgs e)
{
setPlaceholder();
}
private void removePlaceHolder(object sender, EventArgs e)
{
removePlaceHolder();
}
}
This is not my code, but I use it a lot and it works perfect... XAML ONLY
<TextBox x:Name="Textbox" Height="23" Margin="0,17,18.8,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" HorizontalAlignment="Right" ></TextBox>
<TextBlock x:Name="Placeholder" IsHitTestVisible="False" TextWrapping="Wrap" Text="Placeholder Text" VerticalAlignment="Top" Margin="0,20,298.8,0" Foreground="DarkGray" HorizontalAlignment="Right" Width="214">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=Textbox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Attached properties to the rescue:
public static class TextboxExtensions
{
public static readonly DependencyProperty PlaceholderProperty =
DependencyProperty.RegisterAttached(
"Placeholder",
typeof(string),
typeof(TextboxExtensions),
new PropertyMetadata(default(string), propertyChangedCallback: PlaceholderChanged)
);
private static void PlaceholderChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var tb = dependencyObject as TextBox;
if (tb == null)
return;
tb.LostFocus -= OnLostFocus;
tb.GotFocus -= OnGotFocus;
if (args.NewValue != null)
{
tb.GotFocus += OnGotFocus;
tb.LostFocus += OnLostFocus;
}
SetPlaceholder(dependencyObject, args.NewValue as string);
if (!tb.IsFocused)
ShowPlaceholder(tb);
}
private static void OnLostFocus(object sender, RoutedEventArgs routedEventArgs)
{
ShowPlaceholder(sender as TextBox);
}
private static void OnGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
HidePlaceholder(sender as TextBox);
}
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static void SetPlaceholder(DependencyObject element, string value)
{
element.SetValue(PlaceholderProperty, value);
}
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static string GetPlaceholder(DependencyObject element)
{
return (string)element.GetValue(PlaceholderProperty);
}
private static void ShowPlaceholder(TextBox textBox)
{
if (string.IsNullOrWhiteSpace(textBox.Text))
{
textBox.Text = GetPlaceholder(textBox);
}
}
private static void HidePlaceholder(TextBox textBox)
{
string placeholderText = GetPlaceholder(textBox);
if (textBox.Text == placeholderText)
textBox.Text = string.Empty;
}
}
Usage:
<TextBox Text="hi" local:TextboxExtensions.Placeholder="Hello there"></TextBox>
While using the EM_SETCUEBANNER message is probably simplest, one thing I do not like is that the placeholder text disappears when the control gets focus. That's a pet peeve of mine when I'm filling out forms. I have to click off of it to remember what the field is for.
So here is another solution for WinForms. It overlays a Label on top of the control, which disappears only when the user starts typing.
It's certainly not bulletproof. It accepts any Control, but I've only tested with a TextBox. It may need modification to work with some controls. The method returns the Label control in case you need to modify it a bit in a specific case, but that may never be needed.
Use it like this:
SetPlaceholder(txtSearch, "Type what you're searching for");
Here is the method:
/// <summary>
/// Sets placeholder text on a control (may not work for some controls)
/// </summary>
/// <param name="control">The control to set the placeholder on</param>
/// <param name="text">The text to display as the placeholder</param>
/// <returns>The newly-created placeholder Label</returns>
public static Label SetPlaceholder(Control control, string text) {
var placeholder = new Label {
Text = text,
Font = control.Font,
ForeColor = Color.Gray,
BackColor = Color.Transparent,
Cursor = Cursors.IBeam,
Margin = Padding.Empty,
//get rid of the left margin that all labels have
FlatStyle = FlatStyle.System,
AutoSize = false,
//Leave 1px on the left so we can see the blinking cursor
Size = new Size(control.Size.Width - 1, control.Size.Height),
Location = new Point(control.Location.X + 1, control.Location.Y)
};
//when clicking on the label, pass focus to the control
placeholder.Click += (sender, args) => { control.Focus(); };
//disappear when the user starts typing
control.TextChanged += (sender, args) => {
placeholder.Visible = string.IsNullOrEmpty(control.Text);
};
//stay the same size/location as the control
EventHandler updateSize = (sender, args) => {
placeholder.Location = new Point(control.Location.X + 1, control.Location.Y);
placeholder.Size = new Size(control.Size.Width - 1, control.Size.Height);
};
control.SizeChanged += updateSize;
control.LocationChanged += updateSize;
control.Parent.Controls.Add(placeholder);
placeholder.BringToFront();
return placeholder;
}
Based on ExceptionLimeCat's answer, an improvement:
Color farbe;
string ph = "Placeholder-Text";
private void Form1_Load(object sender, EventArgs e)
{
farbe = myTxtbx.ForeColor;
myTxtbx.GotFocus += RemoveText;
myTxtbx.LostFocus += AddText;
myTxtbx.Text = ph;
}
public void RemoveText(object sender, EventArgs e)
{
myTxtbx.ForeColor = farbe;
if (myTxtbx.Text == ph)
myTxtbx.Text = "";
}
public void AddText(object sender, EventArgs e)
{
if (String.IsNullOrWhiteSpace(myTxtbx.Text))
{
myTxtbx.ForeColor = Color.Gray;
myTxtbx.Text = ph;
}
}
I know this is an old thread, but .NET Core and .NET 5.0 have implemented the TextBox.PlaceholderText Property.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.textbox.placeholdertext?view=net-5.0
This would mean you have a button which allows you to do an action, such as logging in or something. Before you do the action you check if the textbox is filled in. If not it will replace the text
private void button_Click(object sender, EventArgs e)
{
string textBoxText = textBox.Text;
if (String.IsNullOrWhiteSpace(textBoxText))
{
textBox.Text = "Fill in the textbox";
}
}
private void textBox_Enter(object sender, EventArgs e)
{
TextBox currentTextbox = sender as TextBox;
if (currentTextbox.Text == "Fill in the textbox")
{
currentTextbox.Text = "";
}
}
It's kind of cheesy but checking the text for the value you're giving it is the best I can do atm, not that good at c# to get a better solution.
You can get the default Template, modify it by overlaying a TextBlock, and use a Style to add triggers that hide and show it in the right states.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace App_name
{
public class CustomTextBox : TextBox
{
private string Text_ = "";
public CustomTextBox() : base()
{}
public string setHint
{
get { return Text_; }
set { Text_ = value; }
}
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (Text_.Equals(this.Text))
this.Clear();
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (String.IsNullOrWhiteSpace(this.Text))
this.Text = Text_;
}
}
}
> xmlns:local="clr-namespace:app_name"
> <local:CustomTextBox
> x:Name="id_number_txt"
> Width="240px"
> Height="auto"/>
Here I come with this solution inspired by #Kemal Karadag.
I noticed that every solution posted here is relying on the focus,
While I wanted my placeholder to be the exact clone of a standard HTML placeholder in Google Chrome.
Instead of hiding/showing the placeholder when the box is focused,
I hide/show the placeholder depending on the text length of the box:
If the box is empty, the placeholder is shown, and if you type in the box, the placeholder disappears.
As it is inherited from a standard TextBox, you can find it in your Toolbox!
using System;
using System.Drawing;
using System.Windows.Forms;
public class PlaceHolderTextBox : TextBox
{
private bool isPlaceHolder = true;
private string placeHolderText;
public string PlaceHolderText
{
get { return placeHolderText; }
set
{
placeHolderText = value;
SetPlaceholder();
}
}
public PlaceHolderTextBox()
{
TextChanged += OnTextChanged;
}
private void SetPlaceholder()
{
if (!isPlaceHolder)
{
this.Text = placeHolderText;
this.ForeColor = Color.Gray;
isPlaceHolder = true;
}
}
private void RemovePlaceHolder()
{
if (isPlaceHolder)
{
this.Text = this.Text[0].ToString(); // Remove placeHolder text, but keep the character we just entered
this.Select(1, 0); // Place the caret after the character we just entered
this.ForeColor = System.Drawing.SystemColors.WindowText;
isPlaceHolder = false;
}
}
private void OnTextChanged(object sender, EventArgs e)
{
if (this.Text.Length == 0)
{
SetPlaceholder();
}
else
{
RemovePlaceHolder();
}
}
}
This is a extension method for the texbox. Simply Add the Placeholder Text programmatically:
myTextBox.AddPlaceholderText("Hello World!");
The extension method:
public static void AddPlaceholderText(this TextBox textBox, string placeholderText)
{
if (string.IsNullOrWhiteSpace(textBox.Text))
textBox.Text = placeholderText;
textBox.SetResourceReference(Control.ForegroundProperty,
textBox.Text != placeholderText
? "SystemControlForegroundBaseHighBrush"
: "SystemControlForegroundBaseMediumBrush");
var ignoreSelectionChanged = false;
textBox.SelectionChanged += (sender, args) =>
{
if (ignoreSelectionChanged) { ignoreSelectionChanged = false; return; }
if (textBox.Text != placeholderText) return;
ignoreSelectionChanged = true;
textBox.Select(0, 0);
};
var lastText = textBox.Text;
var ignoreTextChanged = false;
textBox.TextChanged += (sender, args) =>
{
if (ignoreTextChanged) { ignoreTextChanged = false; return; }
if (string.IsNullOrWhiteSpace(textBox.Text))
{
ignoreTextChanged = true;
textBox.Text = placeholderText;
textBox.Select(0, 0);
}
else if (lastText == placeholderText)
{
ignoreTextChanged = true;
textBox.Text = textBox.Text.Substring(0, 1);
textBox.Select(1, 0);
}
textBox.SetResourceReference(Control.ForegroundProperty,
textBox.Text != placeholderText
? "SystemControlForegroundBaseHighBrush"
: "SystemControlForegroundBaseMediumBrush");
lastText = textBox.Text;
};
}
Happy coding, BierDav
I came up with a method that worked for me, but only because I was willing to use the textbox name as my placeholder. See below.
public TextBox employee = new TextBox();
private void InitializeHomeComponent()
{
//
//employee
//
this.employee.Name = "Caller Name";
this.employee.Text = "Caller Name";
this.employee.BackColor = System.Drawing.SystemColors.InactiveBorder;
this.employee.Location = new System.Drawing.Point(5, 160);
this.employee.Size = new System.Drawing.Size(190, 30);
this.employee.TabStop = false;
this.Controls.Add(employee);
// I loop through all of my textboxes giving them the same function
foreach (Control C in this.Controls)
{
if (C.GetType() == typeof(System.Windows.Forms.TextBox))
{
C.GotFocus += g_GotFocus;
C.LostFocus += g_LostFocus;
}
}
}
private void g_GotFocus(object sender, EventArgs e)
{
var tbox = sender as TextBox;
tbox.Text = "";
}
private void g_LostFocus(object sender, EventArgs e)
{
var tbox = sender as TextBox;
if (tbox.Text == "")
{
tbox.Text = tbox.Name;
}
}
Try the following code:
<TextBox x:Name="InvoiceDate" Text="" Width="300" TextAlignment="Left" Height="30" Grid.Row="0" Grid.Column="3" Grid.ColumnSpan="2" />
<TextBlock IsHitTestVisible="False" Text="Men att läsa" Width="300" TextAlignment="Left" Height="30" Grid.Row="0" Grid.Column="3" Grid.ColumnSpan="2" Padding="5, 5, 5, 5" Foreground="LightGray">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=InvoiceDate}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=InvoiceDate, Path=IsFocused}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
you can also do that when the mouse clicks, let's suppose your placeholder text is "User_Name"
private void textBox1_MouseClick(object sender, MouseEventArgs e)
{
if(textBox1.Text == "User_Name")
textBox1.Text = "";
}
public void Initialize()
{
SetPlaceHolder(loginTextBox, " Логин ");
SetPlaceHolder(passwordTextBox, " Пароль ");
}
public void SetPlaceHolder(Control control, string PlaceHolderText)
{
control.Text = PlaceHolderText;
control.GotFocus += delegate(object sender, EventArgs args) {
if (control.Text == PlaceHolderText)
{
control.Text = "";
}
};
control.LostFocus += delegate(object sender, EventArgs args){
if (control.Text.Length == 0)
{
control.Text = PlaceHolderText;
}
};
}
Instead of using the .Text property of a TextBox, I overlayed a TextBlock with the placeholder.
I couldn't use the .Text property because this was binded to an Event.
XAML:
<Canvas Name="placeHolderCanvas">
<TextBox AcceptsReturn="True" Name="txtAddress" Height="50" Width="{Binding ActualWidth, ElementName=placeHolderCanvas}"
Tag="Please enter your address"/>
</Canvas>
VB.NET
Public Shared Sub InitPlaceholder(canvas As Canvas)
Dim txt As TextBox = canvas.Children.OfType(Of TextBox).First()
Dim placeHolderLabel = New TextBlock() With {.Text = txt.Tag,
.Foreground = New SolidColorBrush(Color.FromRgb(&H77, &H77, &H77)),
.IsHitTestVisible = False}
Canvas.SetLeft(placeHolderLabel, 3)
Canvas.SetTop(placeHolderLabel, 1)
canvas.Children.Add(placeHolderLabel)
AddHandler txt.TextChanged, Sub() placeHolderLabel.Visibility = If(txt.Text = "", Visibility.Visible, Visibility.Hidden)
End Sub
Result:
You could also try in this way..
call the function
TextboxPlaceHolder(this.textBox1, "YourPlaceHolder");
write this function
private void TextboxPlaceHolder(Control control, string PlaceHolderText)
{
control.Text = PlaceHolderText;
control.GotFocus += delegate (object sender, EventArgs args)
{
if (cusmode == false)
{
control.Text = control.Text == PlaceHolderText ? string.Empty : control.Text;
//IF Focus TextBox forecolor Black
control.ForeColor = Color.Black;
}
};
control.LostFocus += delegate (object sender, EventArgs args)
{
if (string.IsNullOrWhiteSpace(control.Text) == true)
{
control.Text = PlaceHolderText;
//If not focus TextBox forecolor to gray
control.ForeColor = Color.Gray;
}
};
}
there are BETTER solutions, but the easiest solution is here:
set the textbox text to your desired string
then create a function that deletes the text, have that function fire on textbox Focus Enter event
I wrote a reusable custom control, maybe it can help someone that need to implement multiple placeholder textboxes in his project. here is the custom class with implementation example of an instance, you can test easily by pasting this code on a new winforms project using VS:
namespace reusebleplaceholdertextbox
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// implementation
CustomPlaceHolderTextbox myCustomTxt = new CustomPlaceHolderTextbox(
"Please Write Text Here...", Color.Gray, new Font("ARIAL", 11, FontStyle.Italic)
, Color.Black, new Font("ARIAL", 11, FontStyle.Regular)
);
myCustomTxt.Multiline = true;
myCustomTxt.Size = new Size(200, 50);
myCustomTxt.Location = new Point(10, 10);
this.Controls.Add(myCustomTxt);
}
}
class CustomPlaceHolderTextbox : System.Windows.Forms.TextBox
{
public string PlaceholderText { get; private set; }
public Color PlaceholderForeColor { get; private set; }
public Font PlaceholderFont { get; private set; }
public Color TextForeColor { get; private set; }
public Font TextFont { get; private set; }
public CustomPlaceHolderTextbox(string placeholdertext, Color placeholderforecolor,
Font placeholderfont, Color textforecolor, Font textfont)
{
this.PlaceholderText = placeholdertext;
this.PlaceholderFont = placeholderfont;
this.PlaceholderForeColor = placeholderforecolor;
this.PlaceholderFont = placeholderfont;
this.TextForeColor = textforecolor;
this.TextFont = textfont;
if (!string.IsNullOrEmpty(this.PlaceholderText))
{
SetPlaceHolder(true);
this.Update();
}
}
private void SetPlaceHolder(bool addEvents)
{
if (addEvents)
{
this.LostFocus += txt_lostfocus;
this.Click += txt_click;
}
this.Text = PlaceholderText;
this.ForeColor = PlaceholderForeColor;
this.Font = PlaceholderFont;
}
private void txt_click(object sender, EventArgs e)
{
// IsNotFirstClickOnThis:
// if there is no other control in the form
// we will have a problem after the first load
// because we dont other focusable control to move the focus to
// and we dont want to remove the place holder
// only on first time the place holder will be removed by click event
RemovePlaceHolder();
this.GotFocus += txt_focus;
// no need for this event listener now
this.Click -= txt_click;
}
private void RemovePlaceHolder()
{
this.Text = "";
this.ForeColor = TextForeColor;
this.Font = TextFont;
}
private void txt_lostfocus(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(this.Text))
{
// set placeholder again
SetPlaceHolder(false);
}
}
private void txt_focus(object sender, EventArgs e)
{
if (this.Text == PlaceholderText)
{
// IsNotFirstClickOnThis:
// if there is no other control in the form
// we will have a problem after the first load
// because we dont other focusable control to move the focus to
// and we dont want to remove the place holder
RemovePlaceHolder();
}
}
}
}
Let's extend the TextBox with PlcaeHoldText and PlaceHoldBackround. I stripped some code form my project.
say goodbye to Grid or Canvas!
<TextBox x:Class="VcpkgGui.View.PlaceHoldedTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:VcpkgGui.View"
mc:Ignorable="d"
Name="placeHoldTextBox"
TextAlignment="Left"
>
<TextBox.Resources>
<local:FrameworkWidthConverter x:Key="getElemWidth"/>
<local:FrameworkHeightConverter x:Key="getElemHeight"/>
<VisualBrush x:Key="PlaceHoldTextBrush" TileMode="None" Stretch="None" AlignmentX="Left" AlignmentY="Center" Opacity="1">
<VisualBrush.Visual>
<Border Background="{Binding ElementName=placeHoldTextBox, Path=PlaceHoldBackground}"
BorderThickness="0"
Margin="0,0,0,0"
Width="{Binding Mode=OneWay, ElementName=placeHoldTextBox, Converter={StaticResource getElemWidth}}"
Height="{Binding Mode=OneWay, ElementName=placeHoldTextBox, Converter={StaticResource getElemHeight}}"
>
<Label Content="{Binding ElementName=placeHoldTextBox, Path=PlaceHoldText}"
Background="Transparent"
Foreground="#88000000"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
ClipToBounds="True"
Padding="0,0,0,0"
FontSize="14"
FontStyle="Normal"
Opacity="1"/>
</Border>
</VisualBrush.Visual>
</VisualBrush>
</TextBox.Resources>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource PlaceHoldTextBrush}"/>
</Trigger>
<Trigger Property="Text" Value="">
<Setter Property="Background" Value="{StaticResource PlaceHoldTextBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace VcpkgGui.View
{
/// <summary>
/// PlaceHoldedTextBox.xaml 的交互逻辑
/// </summary>
public partial class PlaceHoldedTextBox : TextBox
{
public string PlaceHoldText
{
get { return (string)GetValue(PlaceHoldTextProperty); }
set { SetValue(PlaceHoldTextProperty, value); }
}
// Using a DependencyProperty as the backing store for PlaceHolderText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlaceHoldTextProperty =
DependencyProperty.Register("PlaceHoldText", typeof(string), typeof(PlaceHoldedTextBox), new PropertyMetadata(string.Empty));
public Brush PlaceHoldBackground
{
get { return (Brush)GetValue(PlaceHoldBackgroundProperty); }
set { SetValue(PlaceHoldBackgroundProperty, value); }
}
public static readonly DependencyProperty PlaceHoldBackgroundProperty =
DependencyProperty.Register(nameof(PlaceHoldBackground), typeof(Brush), typeof(PlaceHoldedTextBox), new PropertyMetadata(Brushes.White));
public PlaceHoldedTextBox() :base()
{
InitializeComponent();
}
}
[ValueConversion(typeof(FrameworkElement), typeof(double))]
internal class FrameworkWidthConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is FrameworkElement elem)
return double.IsNaN(elem.Width) ? elem.ActualWidth : elem.Width;
else
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
[ValueConversion(typeof(FrameworkElement), typeof(double))]
internal class FrameworkHeightConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is FrameworkElement elem)
return double.IsNaN(elem.Height) ? elem.ActualHeight : elem.Height;
else
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
}
txtUsuario.Attributes.Add("placeholder", "Texto");
Very effective solution here for WindowsForms TextBox control. (not sure about XAML).
This will work in Multliline mode also.
Probably it may be extended for other controls, like ComboBox control (not checked)
Works like a charm.
public class WTextBox : TextBox
{
private string _placeholder;
[Category("Appearance")]
public string Placeholder
{
get { return _placeholder; }
set
{
_placeholder = value ?? string.Empty;
Invalidate();
}
}
public WTextBox()
{
_placeholder = string.Empty;
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg != 0xF || Focused || !string.IsNullOrEmpty(Text) || string.IsNullOrWhiteSpace(_placeholder))
{
return;
}
using (var g = CreateGraphics())
{
TextRenderer.DrawText(g, _placeholder, Font, ClientRectangle, SystemColors.GrayText, BackColor, TextFormatFlags.Left);
}
}
}