WPF Drag Adorner fix movement and show feedback in Adorner - c#

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!

Related

WPF Custom Control's Dependency Property unintentionally affecting other controls

I am making a window which is supposed to record the mouse position. I have created a user control which is supposed to display the current coordinates for that position tag and allow the user to change them.
It works great except for the fact that when one is updated they all change.
I think this has something to do with the fact that dependency properties are statically registered. They need to be dependency properties because I need to be able to Bind them to my model from the xaml.
How can I have the User Controls independent from one another?
<UserControl x:Class="SapFormFiller.SerializableMouseEditorControl"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60*"/>
...
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding LabelText}"></Label>
<Label Grid.Column="1" Content="{Binding SerializableMouseKeyboardEventArgs.X}"/>
<Label Grid.Column="2" Content="{Binding SerializableMouseKeyboardEventArgs.Y}"/>
<Button Grid.Column="3" Margin="0,0,0.4,0" Click="ButtonBase_OnClick">Edit</Button>
</Grid>
cs:
public partial class SerializableMouseEditorControl : UserControl
{
public static DependencyProperty LabelTextProperty = DependencyProperty.Register(
"LabelText", typeof (string), typeof (SerializableMouseEditorControl), new PropertyMetadata(default(string)));
public string LabelText
{
get { return (string) GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public static DependencyProperty SerializableMouseKeyboardEventArgsProperty = DependencyProperty.Register(
"SerializableMouseKeyboardEventArgs", typeof (SerializableMouseKeyboardEventArgs), typeof (SerializableMouseEditorControl), new PropertyMetadata(new SerializableMouseKeyboardEventArgs()));
public SerializableMouseKeyboardEventArgs SerializableMouseKeyboardEventArgs
{
get { return (SerializableMouseKeyboardEventArgs) GetValue(SerializableMouseKeyboardEventArgsProperty); }
set { SetValue(SerializableMouseKeyboardEventArgsProperty, value); }
}
public SerializableMouseEditorControl()
{
InitializeComponent();
SerializableMouseKeyboardEventArgs = new SerializableMouseKeyboardEventArgs();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
SerializableMouseKeyboardEventArgs.Update();
}
}
SerializableMouseKeyboardEventArgs:
public class SerializableMouseKeyboardEventArgs : INotifyPropertyChanged
{
public int X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged("X");
}
}
public int Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged("Y");
}
}
...
IKeyboardMouseEvents gkme;
private int _x = 0;
private int _y=0;
public override string ToString(){...}
public SerializableMouseKeyboardEventArgs()
{
gkme = Hook.GlobalEvents();
}
public void Update()
{
IsEditing = true;
gkme.MouseClick += Gkme_MouseClick;
}
private void Gkme_MouseClick(object sender, MouseEventArgs e)
{
if(e.Button==MouseButtons.Left)
{
this.X = e.X;
this.Y = e.Y;
}
else if (e.Button == MouseButtons.Right)
{
gkme.MouseClick -= Gkme_MouseClick;
IsEditing = false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This line
new PropertyMetadata(new SerializableMouseKeyboardEventArgs()));
looks like it could be causing you problems. I was in a situation where declaring default values using new keyword was causing one instance of the UserControl to share that property value with another instance.
Try initializing any such properties in your constructor and it might work.

Mouse Position with respect to image in WPF using MVVM

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>

Slide images in ListBox

I have collection of images in listBox
<ListBox Name="lb" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0" ScrollViewer.HorizontalScrollBarVisibility="Auto"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Width="150" Height="100"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Visible item is only selected one.
How can I slide or scroll images? Or how manage what item should be selected if I drag cursor on visible image to the left or to the right?
I implemented something similar in past project
Here are some bits of the code. More or less it will work as expected, but you may always adjust it as per your needs
This implements behavior of bringing selected item to view by scrolling to desired position and also implement sliding of item/image to left or right.
Listbox/ItemsControl (Binding is not important but useful for dynamic items)
<ItemsControl ItemsSource="{Binding}">
<i:Interaction.Behaviors>
<behavior:BringSelectionToViewBehavior SelectedItem="{Binding CurrentPage,Mode=TwoWay}" />
</i:Interaction.Behaviors>
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<ScrollViewer HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
BringSelectionToViewBehavior.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Input;
using Microsoft.Expression.Interactivity;
namespace MyProject.Behaviors
{
public class BringSelectionToViewBehavior : Behavior<ItemsControl>
{
Queue<Point> velocityPoints = new Queue<Point>(4);
Point PreviousPoint { get; set; }
DispatcherTimer valocityTimer = new DispatcherTimer();
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BringSelectionToViewBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AddHandler(ItemsControl.SizeChangedEvent, (SizeChangedEventHandler)OnWidthChanged);
AssociatedObject.LayoutUpdated += new EventHandler(AssociatedObject_LayoutUpdated);
AssociatedObject.ReleaseMouseCapture();
valocityTimer.Interval = TimeSpan.FromMilliseconds(20);
valocityTimer.Tick += valocityTimer_Tick;
}
ItemsPresenter itemsPresenter;
void AssociatedObject_LayoutUpdated(object sender, EventArgs e)
{
if (itemsPresenter == null)
{
itemsPresenter = AssociatedObject.FindChildrenByType<ItemsPresenter>().ToList().First();
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
itemsPresenter.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(ItemPresenter_MouseLeftButtonDown), false);
itemsPresenter.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(ItemPresenter_MouseLeftButtonUp), true);
}
}
public void OnWidthChanged(object s, SizeChangedEventArgs e)
{
Dispatcher.BeginInvoke((Action<ItemsControl, object>)QueueAnimation, DispatcherPriority.Render, new[] { AssociatedObject, SelectedItem });
}
private void QueueAnimation(ItemsControl control, object item)
{
BringItemToView(item);
}
public static void OnSelectedItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
return;
BringSelectionToViewBehavior behavior = s as BringSelectionToViewBehavior;
if (!behavior.valocityOverride.HasValue)
behavior.BringItemToView(e.NewValue);
}
private void BringItemToView(object item, EasingMode easeMode = EasingMode.EaseInOut)
{
if (item == null || AssociatedObject == null)
return;
FrameworkElement element = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
BringElementToView(element, easeMode);
}
private void BringElementToView(FrameworkElement element, EasingMode easeMode)
{
if (element == null)
return;
ScrollViewer scrollViewer = element.FindAncestorByType<ScrollViewer>();
if (scrollViewer != null)
{
Point relativePoint = element.TransformToAncestor(scrollViewer).Transform(new Point(0, 0));
ScrollToPosition(scrollViewer, relativePoint.X, relativePoint.Y, easeMode);
}
else
{
element.BringIntoView();
}
}
private void ScrollToPosition(ScrollViewer viewer, double x, double y, EasingMode easeMode)
{
Storyboard sb = new Storyboard();
if (y != 0)
{
DoubleAnimation vertAnim = new DoubleAnimation();
//if (applyEase)
vertAnim.EasingFunction = new CubicEase() { EasingMode = easeMode };
vertAnim.From = viewer.VerticalOffset;
vertAnim.By = y;
vertAnim.Duration = GetAnimationDuration(y);
sb.Children.Add(vertAnim);
Storyboard.SetTarget(vertAnim, viewer);
Storyboard.SetTargetProperty(vertAnim, new PropertyPath(BringSelectionToViewBehavior.VerticalOffsetProperty));
}
if (x != 0)
{
DoubleAnimation horzAnim = new DoubleAnimation();
horzAnim.From = viewer.HorizontalOffset;
horzAnim.By = x;
horzAnim.Duration = GetAnimationDuration(x);
horzAnim.EasingFunction = new CubicEase() { EasingMode = easeMode };
sb.Children.Add(horzAnim);
Storyboard.SetTarget(horzAnim, viewer);
Storyboard.SetTargetProperty(horzAnim, new PropertyPath(BringSelectionToViewBehavior.HorizontalOffsetProperty));
}
//overrideDuretion = false;
sb.Completed += new EventHandler(sb_Completed);
sb.Begin();
}
void sb_Completed(object sender, EventArgs e)
{
((sender as ClockGroup).Timeline as Storyboard).Remove();
valocityOverride = null;
}
double? valocityOverride;
private Duration GetAnimationDuration(double distanceToTravel)
{
double animTime = Math.Abs(distanceToTravel) * .7;
if (valocityOverride.HasValue && valocityOverride > 50)
{
animTime = Math.Abs(distanceToTravel) * (valocityOverride.Value / 1000);
}
if (animTime > 1500)
animTime = 1500;
if (animTime < 250)
animTime = 250;
return new Duration(TimeSpan.FromMilliseconds(animTime));
}
public static double GetVerticalOffset(DependencyObject obj)
{
return (double)obj.GetValue(VerticalOffsetProperty);
}
public static void SetVerticalOffset(DependencyObject obj, double value)
{
obj.SetValue(VerticalOffsetProperty, value);
}
// Using a DependencyProperty as the backing store for VerticalOffset. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(BringSelectionToViewBehavior), new PropertyMetadata(new PropertyChangedCallback(OnVerticalChanged)));
public static double GetHorizontalOffset(DependencyObject obj)
{
return (double)obj.GetValue(HorizontalOffsetProperty);
}
public static void SetHorizontalOffset(DependencyObject obj, double value)
{
obj.SetValue(HorizontalOffsetProperty, value);
}
// Using a DependencyProperty as the backing store for HorizontalOffset. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached("HorizontalOffset", typeof(double), typeof(BringSelectionToViewBehavior), new PropertyMetadata(new PropertyChangedCallback(OnHorizontalChanged)));
private static void OnVerticalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer viewer = d as ScrollViewer;
viewer.ScrollToVerticalOffset((double)e.NewValue);
}
private static void OnHorizontalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer viewer = d as ScrollViewer;
viewer.ScrollToHorizontalOffset((double)e.NewValue);
}
void valocityTimer_Tick(object sender, EventArgs e)
{
Point currentPoint = Mouse.GetPosition(AssociatedObject);
velocityPoints.Enqueue(currentPoint);
if (velocityPoints.Count > 4)
velocityPoints.Dequeue();
}
private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (itemsPresenter.IsMouseCaptured)
{
Point CurrentPoint = e.GetPosition(AssociatedObject);
Vector offset = PreviousPoint - CurrentPoint;
PreviousPoint = CurrentPoint;
if (offset.X != 0)
{
Viewer.ScrollToHorizontalOffset(Viewer.HorizontalOffset + offset.X);
}
}
}
public ScrollViewer Viewer { get; set; }
/// <summary>
/// Handles the MouseLeftButtonDown event of the ItemPresenter control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
private void ItemPresenter_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//Viewer.ApplyAnimationClock(BringSelectionToViewBehavior.HorizontalOffsetProperty, null);
Border bd = (e.OriginalSource as FrameworkElement).FindAncestorByType<Border>();
if (bd == null || (bd != null && bd.Name != "GripBarElement"))
{
Viewer = AssociatedObject.FindChildrenByType<ScrollViewer>().ToList().First();
PreviousPoint = e.GetPosition(AssociatedObject);
itemsPresenter.CaptureMouse();
velocityPoints.Clear();
valocityTimer.Start();
}
}
private double CalculateMouseSpeed()
{
if (velocityPoints.Count > 1)
{
Point first = velocityPoints.Dequeue();
return velocityPoints.Aggregate<Point, Point, double>(first, (s, pt) => new Point(s.X, pt.X), pt => pt.X - pt.Y);
}
return 0;
}
/// <summary>
/// Handles the MouseLeftButtonUp event of the ItemPresenter control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
private void ItemPresenter_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (itemsPresenter.IsMouseCaptured)
{
if (Viewer == null)
return;
valocityTimer.Stop();
itemsPresenter.ReleaseMouseCapture();
double MouseSpeed = CalculateMouseSpeed() / 3;
double newtotal = 5 * MouseSpeed;
double newOffset = Viewer.HorizontalOffset + newtotal;
double maxOffset = itemsPresenter.ActualWidth - (itemsPresenter.ActualWidth / AssociatedObject.Items.Count);
if (newOffset < 0)
{
newtotal += 0 - newOffset;
}
else if (newOffset > maxOffset)
{
newtotal -= newOffset - maxOffset;
}
newOffset = Viewer.HorizontalOffset + newtotal;
int newIndex = (int)System.Math.Round(newOffset / (itemsPresenter.ActualWidth / AssociatedObject.Items.Count));
object objToSet = AssociatedObject.ItemsSource.OfType<object>().ElementAt(newIndex);
if (objToSet != null)
{
valocityOverride = MouseSpeed;
if (!object.Equals(SelectedItem, objToSet))
SelectedItem = objToSet;
BringItemToView(objToSet, EasingMode.EaseOut);
}
}
}
}
}
Give it a try and see if this can solve your issue.

Click-to-Edit Control LostFocus event issue

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>

WPF UserControl property change not updating

I have a UserControl that I add to my main application.
That UserControl contains a button for a UIElement
The UserControl contains a DispatchTimer and every 2 seconds based on some int values determines what the button image will be.
One of the methods called in the UserControl should set it's image but the control never displays the image that it was changed to.
public void SetNormal()
{
btnFlashAlert.Content = new BitmapImage(new Uri("Images/FlashButton.png", UriKind.RelativeOrAbsolute));
}
Is there something i'm missing to get the look of the control update on the main application?
When I look at what .Content contains, it is correct. The UI doesn't reflect the change.
XAML
<UserControl x:Class="SC.FlashSystem.MainButton"
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"
mc:Ignorable="d" Height="53" Width="164">
<Button x:Name="btnFlashAlert" Background="{x:Null}" BorderBrush="{x:Null}" Cursor="Hand" Click="btnFlashAlert_Click">
<Button.Template>
<ControlTemplate>
<Image Source="Images/FlashButton.png"/>
</ControlTemplate>
</Button.Template>
</Button>
Codebehind Updated
public partial class MainButton : UserControl
{
private SupportConsoleWeb.MessageData messageCounts { get; set; }
private readonly DispatcherTimer flashButtonChangeTimer = new DispatcherTimer();
private BitmapImage NormalImage { get; set; }
private BitmapImage CriticalImage { get; set; }
private BitmapImage AlertImage { get; set; }
private BitmapImage InfoImage { get; set; }
public MainButton()
{
InitializeComponent();
messageCounts = new SupportConsoleWeb.MessageData();
messageCounts.CriticalCount = 0;
messageCounts.AlertCount = 0;
messageCounts.InfoCount = 0;
NormalImage = new BitmapImage(new Uri("Images/FlashButton.png", UriKind.RelativeOrAbsolute));
CriticalImage = new BitmapImage(new Uri("Images/FlashButtonRed.png", UriKind.RelativeOrAbsolute));
AlertImage = new BitmapImage(new Uri("Images/FlashButtonOrange.png", UriKind.RelativeOrAbsolute));
InfoImage = new BitmapImage(new Uri("Images/FlashButtonGreen.png", UriKind.RelativeOrAbsolute));
flashButtonChangeTimer.Interval = TimeSpan.FromSeconds(2);
flashButtonChangeTimer.Tick += flashButtonChangeTimer_Tick;
flashButtonChangeTimer.Start();
}
void flashButtonChangeTimer_Tick(object sender, EventArgs e)
{
btnFlashAlert.Dispatcher.BeginInvoke(new Action(() =>
{
if (btnFlashAlert.Content == null)
{
SetNormal();
}
else if (messageCounts.CriticalCount > 0 && btnFlashAlert.Content.Equals(CriticalImage))
{
SetNormal();
}
else if (messageCounts.AlertCount > 0 && btnFlashAlert.Content.Equals(AlertImage))
{
SetNormal();
}
else if (messageCounts.InfoCount > 0 && btnFlashAlert.Content.Equals(InfoImage))
{
SetNormal();
}
else if (messageCounts.CriticalCount > 0)
{
SetCritical();
}
else if (messageCounts.AlertCount > 0)
{
SetAlert();
}
else if (messageCounts.InfoCount > 0)
{
SetInfo();
}
}));
}
public void UpdateMessageCounts(SupportConsoleWeb.MessageData messageCounts)
{
this.messageCounts = messageCounts;
}
private void btnFlashAlert_Click(object sender, RoutedEventArgs e)
{
MainWindow window = new MainWindow();
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
window.ShowDialog();
}
public void SetMessageCount(int criticalCount, int alertCount, int infoCount)
{
messageCounts.CriticalCount = criticalCount;
messageCounts.AlertCount = alertCount;
messageCounts.InfoCount = infoCount;
}
private void SetNormal()
{
btnFlashAlert.Content = NormalImage;
}
private void SetCritical()
{
btnFlashAlert.Content = CriticalImage;
}
private void SetAlert()
{
btnFlashAlert.Content = AlertImage;
}
private void SetInfo()
{
btnFlashAlert.Content = InfoImage;
}
}
Change your XAML To this
<Image Source="{Binding TheImage}"/>
Add notify property changed
public partial class MainButton : UserControl, INotifyPropertyChanged
Create the OnPropertyChanged Event
void OnPropertyChanged(String prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
Create a Bitmap prop and notify the prop changed event
private BitmapImage _TheImage;
public BitmapImage TheImage
{
get { return _TheImage; }
set { _TheImage = value; OnPropertyChanged("TheImage"); }
}
In your initializer
public MainButton()
{
this.DataContext = this;
InitializeComponent();
TheImage = new BitmapImage();
Now in your setting methods call
TheImage = //Your Bitmap Goes here
I know this seems excessive but you will see it is a much cleaner implementation in the long run.
I believe its an issue with picture selection logic not having a default image when none of the conditions are met...
With that said, IMHO the picture logic would be better expressed by having all images pre-loaded and their visibility initially set to hidden. Then bind the visibility of each image to a specific flag boolean on the VM. Which the timer event can simply turn on or off the boolean(s) which will ultimately show or hide images as needed.
That removes any latency due to loading and showing of images for they will be pre-loaded; also it will solve any possible future memory issues due to loading/unloading of images.
Example
The following example has a button with two images. Both image's visibility is bound to Booleans on the VM. The VM has one Boolean which the imageas work off of and a timer which changes its status every two seconds switching the images.
Xaml:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Button x:Name="bStatus" Width="48" Height="48">
<StackPanel Orientation="Vertical">
<Image Source="Images\Copy-icon.png" Visibility="{Binding IsCopyOn,
Converter={StaticResource BooleanToVisibilityConverter}}" />
<Image Source="Images\Recycle-icon.png"
Visibility="{Binding IsRecycleOn,
Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
</Button>
VM
public class MainVM : INotifyPropertyChanged
{
private bool _bSwitch;
private readonly DispatcherTimer flashButtonChangeTimer = new DispatcherTimer();
public bool IsRecycleOn
{
get { return _bSwitch; }
}
public bool IsCopyOn
{
get { return !_bSwitch; }
}
public MainVM()
{
flashButtonChangeTimer.Interval = TimeSpan.FromSeconds(2);
flashButtonChangeTimer.Tick += (sender, args) =>
{
_bSwitch = ! _bSwitch;
OnPropertyChanged("IsCopyOn");
OnPropertyChanged("IsRecycleOn");
};
flashButtonChangeTimer.Start();
}
/// <summary>Event raised when a property changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Raises the PropertyChanged event.</summary>
/// <param name="propertyName">The name of the property that has changed.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Categories