I'm trying to conform to the MVVM structure when getting the mouse position when it is over an image in a wpf application. The mouse position should be converted to a pixel location with respect to the image.
I have this working when Image_MouseMove is in ImagePositionView.xaml.cs, but I'm at a bit of a loss (even after trying to read other threads) as to how to achieve this using the MVVM structure.
I have added a reference to MVVMLight in hopes that this will make this task easier, but I have never used to before..
This is what I have so far:
View:
I have added these references based on what I have seen:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
<UserControl x:Class="ImagePixelLocation.View.ImagePositionView"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
xmlns:local="clr-namespace:ImagePixelLocation"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1000" Background="White">
<Grid>
<Viewbox HorizontalAlignment="Center">
<Grid Name="ColorImage">
<Image x:Name="ImageOnDisplay" Source="{Binding ColourImage}" Stretch="UniformToFill" />
</Grid>
</Viewbox>
</Grid>
</UserControl>
ViewModel:
ViewModelBase exposes INofityPropertyChanged and IDisposable
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GalaSoft.MvvmLight;
namespace ImagePixelView.ViewModel
{
class ImagePositionViewModel : ViewModelBase
{
private WriteableBitmap colourBitmap = null;
public ImageSource ColourImage
{
get
{
return this.colourBitmap;
}
}
public ManualSelectionViewModel()
{
// Open image to writeablebitmap
string path = #"C:\Some\Path\To\ColorImage.png";
Stream imageStreamSource = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource source = decoder.Frames[0];
int width = source.PixelWidth;
int height = source.PixelHeight;
int stride = source.Format.BitsPerPixel / 8 * width;
byte[] data = new byte[stride * height];
source.CopyPixels(data, stride, 0);
this.colourBitmap = new WriteableBitmap(width, height, 96.0, 96.0, source.Format, null);
this.colourBitmap.WritePixels(new Int32Rect(0, 0, width, height), data, stride, 0);
}
private void Image_MouseMove(object sender, MouseEventArgs e)
{
BitmapSource bitmapImage = (BitmapSource)this.ColourImage;
string xCoord = (e.GetPosition(ImageOnDisplay).X * bitmapImage.PixelWidth / ImageOnDisplay.ActualWidth).ToString();
string yCoord = (e.GetPosition(ImageOnDisplay).Y * bitmapImage.PixelHeight / ImageOnDisplay.ActualHeight).ToString();
System.Diagnostics.Debug.WriteLine("mouse location is X:" + xCoord + ", Y:" + yCoord);
}
}
}
I guess the main thing is how do I get access to the view element ImageOnDisplay from within ImagePositionViewModel.
I do this with a behaviour. First I declare an interface that my view model will implement:
public interface IMouseCaptureProxy
{
event EventHandler Capture;
event EventHandler Release;
void OnMouseDown(object sender, MouseCaptureArgs e);
void OnMouseMove(object sender, MouseCaptureArgs e);
void OnMouseUp(object sender, MouseCaptureArgs e);
}
public class MouseCaptureArgs
{
public double X {get; set;}
public double Y { get; set; }
public bool LeftButton { get; set; }
public bool RightButton { get; set; }
}
And here's a behaviour that uses it:
public class MouseCaptureBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty ProxyProperty = DependencyProperty.RegisterAttached(
"Proxy",
typeof(IMouseCaptureProxy),
typeof(MouseCaptureBehavior),
new PropertyMetadata(null, OnProxyChanged));
public static void SetProxy(DependencyObject source, IMouseCaptureProxy value)
{
source.SetValue(ProxyProperty, value);
}
public static IMouseCaptureProxy GetProxy(DependencyObject source)
{
return (IMouseCaptureProxy)source.GetValue(ProxyProperty);
}
private static void OnProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is IMouseCaptureProxy)
{
(e.OldValue as IMouseCaptureProxy).Capture -= OnCapture;
(e.OldValue as IMouseCaptureProxy).Release -= OnRelease;
}
if (e.NewValue is IMouseCaptureProxy)
{
(e.NewValue as IMouseCaptureProxy).Capture += OnCapture;
(e.NewValue as IMouseCaptureProxy).Release += OnRelease;
}
}
static void OnCapture(object sender, EventArgs e)
{
var behavior = sender as MouseCaptureBehavior;
if (behavior != null)
behavior.AssociatedObject.CaptureMouse();
}
static void OnRelease(object sender, EventArgs e)
{
var behavior = sender as MouseCaptureBehavior;
if (behavior != null)
behavior.AssociatedObject.ReleaseMouseCapture();
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreviewMouseDown += OnMouseDown;
this.AssociatedObject.PreviewMouseMove += OnMouseMove;
this.AssociatedObject.PreviewMouseUp += OnMouseUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.PreviewMouseDown -= OnMouseDown;
this.AssociatedObject.PreviewMouseMove -= OnMouseMove;
this.AssociatedObject.PreviewMouseUp -= OnMouseUp;
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs {
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseDown(this, args);
}
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs {
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseMove(this, args);
}
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
var proxy = GetProxy(this);
if (proxy != null)
{
var pos = e.GetPosition(this.AssociatedObject);
var args = new MouseCaptureArgs
{
X = pos.X,
Y = pos.Y,
LeftButton = (e.LeftButton == MouseButtonState.Pressed),
RightButton = (e.RightButton == MouseButtonState.Pressed)
};
proxy.OnMouseUp(this, args);
}
}
}
To use this behaviour you add it to the target UI element and bind to an object that implements the proxy interface. In this case I made the MainViewModel implement the interface so I just bind to that:
<!-- Canvas must have a background, even if it's Transparent -->
<Canvas Background="White" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Behaviors>
<behaviors:MouseCaptureBehavior Proxy="{Binding}" />
</i:Interaction.Behaviors>
The view model now needs to provide mouse handlers which the behaviour will call, it also needs to provide the Capture/Release events which the behaviour will respond to when raised by the view model:
public class MainViewModel : ViewModelBase, IMouseCaptureProxy
{
public event EventHandler Capture;
public event EventHandler Release;
public void OnMouseDown(object sender, MouseCaptureArgs e) {...}
public void OnMouseMove(object sender, MouseCaptureArgs e) {...}
public void OnMouseUp(object sender, MouseCaptureArgs e) {...}
}
UPDATE: It should be self-evident, but just in case not: the sender that you pass in to the Capture and Release events should be the same one you received via the MouseDown/Move/Up handlers. The event args passed to Capture/Receive aren't used and can be null.
Solution for Loading image and display its xy coordinates with rgb values when mouse move on image using MVVM..
Hope this will be help full for some one..
ViewModel:
class MainWindowViewModel : ViewModelBase
{
private Bitmap Img;
public ICommand OpenImg { get; set; }
public MainWindowViewModel()
{
OpenImg = new RelayCommand(openImg, (obj) => true);
}
private void openImg(object obj = null)
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png;*.bmp;*.tiff|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
ImgPath = op.FileName;
Img = new Bitmap(ImgPath);
}
}
private string _ImgPath;
public string ImgPath
{
get
{
return _ImgPath;
}
set
{
_ImgPath = value;
OnPropertyChanged("ImgPath");
}
}
private ICommand _mouseMoveCommand;
public ICommand MouseMoveCommand
{
get
{
if (_mouseMoveCommand == null)
{
_mouseMoveCommand = new RelayCommand(param => ExecuteMouseMove((MouseEventArgs)param));
}
return _mouseMoveCommand;
}
set { _mouseMoveCommand = value; }
}
private void ExecuteMouseMove(MouseEventArgs e)
{
System.Windows.Point p = e.GetPosition(((IInputElement)e.Source));
XY = String.Format("X: {0} Y:{1}", (int)p.X, (int)p.Y);
BitmapData bd = Img.LockBits(new Rectangle(0, 0, Img.Width, Img.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* ptr = (byte*)bd.Scan0;
int x = (int)p.X * 3;
int y = (int)p.Y * bd.Stride;
RGB = "R: "+ptr[x + y + 2].ToString() + " G: " + ptr[x + y + 1].ToString() + " B: " + ptr[x + y].ToString();
}
Img.UnlockBits(bd);
}
private string xy;
public string XY
{
get { return xy; }
set
{
xy = value;
OnPropertyChanged("XY");
}
}
private string rgb;
public string RGB
{
get { return rgb; }
set
{
rgb = value;
OnPropertyChanged("RGB");
}
}
}
MainWindow.xaml
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainWindowViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainWindowViewModel}">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*" />
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Menu FontSize="20">
<MenuItem Header="File">
<MenuItem Header="Open" Command="{Binding OpenImg}"/>
</MenuItem>
</Menu>
</Grid>
<Grid Grid.Row="1" Background="LightGray">
<Viewbox Margin="3,3,3,3">
<Image x:Name="img" Stretch="None" Source="{Binding ImgPath}"
Model:MouseBehaviour.MouseMoveCommand="{Binding MouseMoveCommand}">
</Image>
</Viewbox>
</Grid>
<Grid Grid.Row="2">
<StackPanel Orientation="Horizontal">
<TextBox Focusable="False" Text="{Binding XY}" Width="100"/>
<TextBox Focusable="False" Text="{Binding RGB}" Width="115"/>
</StackPanel>
</Grid>
</Grid>
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 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);
}
}
}