I created a minimal project in order to "get going" with drawing with DrawingVisuals in WPF (being very beginner so far).
My project contains only XAML and code behind for the main window. The only purpose of this project is open a window and display some "signal noise" filling the available space.
MainWindow.xaml is this:
<Window x:Class="MinimalPlotter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MinimalPlotter"
Title="MainWindow" Width="1200" Height="800"
WindowStartupLocation="CenterScreen">
<Canvas Height="500" Width="700" x:Name="drawingArea" Margin="50" Background="Gray">
<local:VisualHost Canvas.Top="0" Canvas.Left="0" x:Name="host" Width="700" Height="500" />
</Canvas>
</Window>
And code behind is this:
namespace MinimalPlotter
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class VisualHost : FrameworkElement
{
public VisualHost()
{
int h = (int)this.Height;
int w = (int)this.Width;
Random random = new Random();
double r;
DrawingVisual path = new DrawingVisual();
StreamGeometry g = new StreamGeometry();
StreamGeometryContext cr = g.Open();
cr.BeginFigure(new Point(0,0), false, false);
for (int i = 0; i < w; i++)
{
// ugly calculations below to get the signal centered in container
r = (random.NextDouble()-0.5) * h -h/2;
cr.LineTo(new Point(i, r), true, false);
}
cr.Close();
g.Freeze();
DrawingContext crx = path.RenderOpen();
Pen p = new Pen(Brushes.Black, 2);
crx.DrawGeometry(null, p, g);
// Ellipse included for "visual debugging"
crx.DrawEllipse(Brushes.Red, p, new Point(50,50), 45, 20);
crx.Close();
this.AddVisualChild(path);
}
}
}
The problem is: when the window opens, the Canvas shows up in the center, as expected (with a gray background), but no signal gets plotted. A previous version of this code worked fine using Path geometry, but with DrawingVisual no geometry is shown (not even the ellipse geometry included for debugging).
Thanks for reading!
Your VisualHost class would also have to override the VisualChildrenCount property and the GetVisualChild method:
public class VisualHost : FrameworkElement
{
private DrawingVisual path = new DrawingVisual();
public VisualHost()
{
...
AddVisualChild(path);
}
protected override int VisualChildrenCount
{
get { return 1; }
}
protected override Visual GetVisualChild(int index)
{
return path;
}
}
Please note also that it is considered good practice to use IDisposable objects like StreamGeometryContext and DrawingContext in using statements:
var g = new StreamGeometry();
using (var cr = g.Open())
{
cr.BeginFigure(new Point(0,0), false, false);
...
// no need for cr.Close()
}
using (var crx = path.RenderOpen())
{
var p = new Pen(Brushes.Black, 2);
crx.DrawGeometry(null, p, g);
crx.DrawEllipse(Brushes.Red, p, new Point(50,50), 45, 20);
// no need for crx.Close()
}
Related
I am rebuilding in wpf my vb6 application that is working perfectly since 2011.
My app handles 11 documents. In vb6 I used 11 forms in an MDI.
In wpf I am using a Canvas that I called Hold. This canvas holds 11 instances of a FrameworkElement that I called Doc.
Doc has methods for drawing shape and text for a class that I called Cell.
In order to place cells in Doc, In need Doc to draw a grid. For that I have a Boolean field (bool _showGrid;) if true Doc draws the grid.
My problem is that Doc FrameworkElement does not draw the grid when called from xaml. But from Window_Loaded it does.
this is a part of the Doc FrameworkElement:
public class Doc : FrameworkElement
{
VisualCollection paper;
DrawingVisual cellMaker;
bool _showGrid;
public Doc()
{
paper = new VisualCollection(this);
//SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
}
public bool showGrid
{
set
{
_showGrid = value;
if (_showGrid)
{
drawGrid();
}
}
}
private void drawGrid()
{
DrawingVisual grid = new DrawingVisual();
using(DrawingContext dc = grid.RenderOpen())
{
for(int i = 0; i <= Width; i += 18)
{
dc.DrawLine(new Pen(Brushes.OrangeRed, 1), new Point(i, 0), new Point(i, Height));
}
for(int j = 0; j <= Height; j += 18)
{
dc.DrawLine(new Pen(Brushes.OrangeRed, 1), new Point(0, j), new Point(Width, j));
}
dc.Close();
}
paper.Add(grid);
}
And this is xaml where documentsReceipt instance of Doc is created in showGrid is set to true witch is not working:
<ScrollViewer Grid.Row="1" Grid.Column="0">
<Canvas Name="Hold" Width="21cm" Height="29.7cm" Background="White" Margin="17">
<dc:Doc Name="documentsReceipt"
Width="{Binding Path=ActualWidth,ElementName=Hold}"
Height="{Binding Path=ActualHeight,ElementName=Hold}"
showGrid="True"
Loaded="documentsReceipt_Loaded">
</dc:Doc>
<TextBox Name="txt"
TextChanged="txt_TextChanged"
KeyDown="txt_KeyDown"
PreviewKeyDown="txt_PreviewKeyDown"/>
</Canvas>
</ScrollViewer>
This is the app with when I omit documentReceipt=true from Window_Loaded
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//documentsReceipt.showGrid = true;
}
Window without grid
And this is the output when I enable showGrid
Window with grid
Usually you implement a control like this a little bit different. First of all you need a dependency property for ShowGrid to be bindable. Next is you override OnRender to draw your shapes (or what ever). Here is the full implementation of the control:
public class Doc : FrameworkElement
{
public bool ShowGrid
{
get { return (bool)GetValue (ShowGridProperty); }
set { SetValue (ShowGridProperty, value); }
}
public static readonly DependencyProperty ShowGridProperty =
DependencyProperty.Register ("ShowGrid", typeof (bool), typeof (Doc), new FrameworkPropertyMetadata (false, FrameworkPropertyMetadataOptions.AffectsRender));
protected override void OnRender (DrawingContext dc)
{
if (ShowGrid)
{
for (int i = 0; i <= ActualWidth; i += 18)
{
dc.DrawLine (new Pen (Brushes.OrangeRed, 1), new Point (i, 0), new Point (i, Height));
}
for (int j = 0; j <= ActualHeight; j += 18)
{
dc.DrawLine (new Pen (Brushes.OrangeRed, 1), new Point (0, j), new Point (Width, j));
}
}
}
}
I’m having some problems with OxyPlot that I have not been able to resolve through their documentation or other searches. I’m working on a wpf application that will allow the user to open a .csv with a button-click event, then perform some math and report back some useful information. I’d like to plot some of the generated data hence OxyPlot. For some reason I cannot get the plot to populate, when the code that generates it, is within the button click event. To illustrate here is a smaller example:
This code works (xaml):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="20,20,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
<Grid HorizontalAlignment="Left" Height="255" Margin="20,47,0,0" VerticalAlignment="Top" Width="477">
<oxy:PlotView Model="{Binding ScatterModel}"/>
</Grid>
</Grid>
with this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
var tmp = new PlotModel { Title = "Scatter plot", Subtitle = "y = x" };
var s2 = new LineSeries
{
StrokeThickness = 1,
MarkerSize = 1,
MarkerStroke = OxyColors.ForestGreen,
MarkerType = MarkerType.Plus
};
for (int i = 0; i < 100; i++)
{
s2.Points.Add(new DataPoint(i, i));
}
tmp.Series.Add(s2);
this.ScatterModel = tmp;
}
private void button_Click(object sender, RoutedEventArgs e)
{
}
public PlotModel ScatterModel { get; set; }
And produces this:
Plot Working
But, without changing the xaml, if I copy/paste the code beneath the button click event:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
DataContext = this;
var tmp = new PlotModel { Title = "Scatter plot", Subtitle = "y = x" };
var s2 = new LineSeries
{
StrokeThickness = 1,
MarkerSize = 1,
MarkerStroke = OxyColors.ForestGreen,
MarkerType = MarkerType.Plus
};
for (int i = 0; i < 100; i++)
{
s2.Points.Add(new DataPoint(i, i));
}
tmp.Series.Add(s2);
this.ScatterModel = tmp;
}
public PlotModel ScatterModel { get; set; }
The plot never generates: Not working:
I’ve tried moving DataContext = this; back up to public MainWindow(), and vice-versa with InitializeComponent(); no change. I’ve also tried defining
<Window.DataContext>
<local:MainWindow/>
</Window.DataContext>
in the xaml but that throws an exception/infinite loop error during build.
Something simple I fear I'm not getting about OxyPlot implementation?
Thanks!
CSMDakota
INotifyPropertyChanged keeps your view in sync with the program's state. One way to do this is by implementing a ViewModel (the MVVM pattern).
So let's create one. ViewModelBase introduces OnPropertyChanged(), the method that updates ScatterModel.
ViewModels.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using OxyPlot;
namespace WpfApplication1
{
public class ViewModel : ViewModelBase
{
private PlotModel _scatterModel;
public PlotModel ScatterModel
{
get { return _scatterModel; }
set
{
if (value != _scatterModel)
{
_scatterModel = value;
OnPropertyChanged();
}
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] String propName = null)
{
// C#6.O
// PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
In MainWindow.xaml you can now add
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
var tmp = new PlotModel { Title = "Scatter plot", Subtitle = "y = x" };
var s2 = new LineSeries
{
StrokeThickness = 1,
MarkerSize = 1,
MarkerStroke = OxyColors.ForestGreen,
MarkerType = MarkerType.Plus
};
for (int i = 0; i < 100; i++)
{
s2.Points.Add(new DataPoint(i, i));
}
tmp.Series.Add(s2);
ViewModel.ScatterModel = tmp;
}
// C#6.O
// public ViewModel ViewModel => (ViewModel)DataContext;
public ViewModel ViewModel
{
get { return (ViewModel)DataContext; }
}
}
Note we're no longer setting DataContext = this, which is considered bad practice. In this case the ViewModel is small, but as a program grows this way of structuring pays off.
I have been tasked with taking an existing list of transparent .png images (currently housed within an ImageList) and displaying them in a WPF DataGrid based on the ImageID column.
I have set up the DataGridColumn as follows:
_dataTemplateColumn = new DataGridTemplateColumn();
_dataTemplateColumn.Header = "";
FrameworkElementFactory _factory = new FrameworkElementFactory(typeof(Image));
Binding _binding = new Binding("Image");
_binding.Mode = BindingMode.TwoWay;
_factory.SetValue(Image.SourceProperty, _binding);
DataTemplate _cellTemplate = new DataTemplate();
_cellTemplate.VisualTree = _factory;
_dataTemplateColumn.CellTemplate = _cellTemplate;
Style _style = new Style();
_style.Setters.Add(new Setter(BackgroundProperty, Brushes.Transparent));
_dataTemplateColumn.CellStyle = _style;
I then create a Custom Object at runtime which includes the image for me and run the following 2 methods on the Image, the first to resize it and the second to convert it into a Bitmap rather than a BitmapImage (which is the only format I have managed to get it working in WPF with so far):
public static Bitmap ResizeImage(this Bitmap Bitmap, Size size)
{
try
{
Bitmap _bitmap = new Bitmap(size.Width, size.Height);
using (Graphics _graphic = Graphics.FromImage((Image)_bitmap))
{
_graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
_graphic.DrawImage(Bitmap, 0, 0, size.Width, size.Height);
}
_bitmap.MakeTransparent(Color.Magenta);
return _bitmap;
}
catch (Exception ex)
{
throw ex;
}
}
public static Bitmap ToBitmap(this BitmapImage BitmapImage)
{
using (MemoryStream _stream = new MemoryStream())
{
BitmapEncoder _encoder = new BmpBitmapEncoder();
_encoder.Frames.Add(BitmapFrame.Create(BitmapImage));
_encoder.Save(_stream);
System.Drawing.Bitmap _bitmap = new System.Drawing.Bitmap(_stream);
_bitmap.MakeTransparent(Color.Magenta);
return new Bitmap(_bitmap);
}
}
The Image is being displayed in the correct size and position in the DataGrid but the transparency is not preserved from the .png format. If anyone knows a better method for me (perhaps it is more correct to take the Image into a resource file first for example?) or a way to get the transparency working within my current code it would be most appreciated!
The following example gives you an idea of how it may look like:
XAML:
<Window ...>
<Window.Resources>
<DataTemplate x:Key="ImageCellTemplate">
<Image Source="{Binding Image}" Width="100"/>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False"/>
</Grid>
</Window>
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var col = new DataGridTemplateColumn();
col.CellTemplate = (DataTemplate)Resources["ImageCellTemplate"];
dataGrid.Columns.Add(col);
foreach (var file in Directory.EnumerateFiles(#"C:\Users\Public\Pictures\Sample Pictures", "*.jpg"))
{
dataGrid.Items.Add(new DataItem { Image = file });
}
}
}
public class DataItem
{
public string Image { get; set; }
}
I am animating a TextBlock. In 60 seconds, it increases FontSize from 8pt to 200pt. Everything is working fine, except that my animation is moving up and down a bit as the text grows. Why is this happening and is it possible to avoid this?
I have a very simple XAML file:
<Window x:Class="Timer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800"
Height="500"
Title="MainWindow"
Loaded="Window_Loaded">
<Grid>
<TextBlock
Name="TimerTextBlock"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="00h : 00m : 00.000s" />
</Grid>
</Window>
And equally simple code-behind:
public partial class MainWindow : Window
{
private const string timerFormat = "{0:hh'h : 'mm'm : 'ss'.'fff's'}";
private DispatcherTimer dispatcherTimer;
private DateTime targetTime;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
targetTime = DateTime.Now.AddSeconds(60);
double totalTime = targetTime.Subtract(DateTime.Now).TotalMilliseconds;
DoubleAnimation animation = new DoubleAnimation();
animation.From = TimerTextBlock.FontSize;
animation.To = 200;
animation.Duration = new Duration(targetTime.Subtract(DateTime.Now));
TimerTextBlock.BeginAnimation(TextBlock.FontSizeProperty, animation);
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(1);
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Start();
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
if (DateTime.Compare(targetTime, DateTime.Now) > 0)
{
TimerTextBlock.Text =
string.Format(timerFormat, targetTime.Subtract(DateTime.Now));
}
}
}
Thank you for all the clarifications.
Your vertical jumping problem is due to font rendering rounding. Specifically, WPF will avoid subpixel font height in order to enable font smoothing. One way to avoid this is to convert your text into a path geometry and then use a scale transform to animate it.
Here is an alternate version of your example without the jumping. The new XAML is:
<Grid>
<Path Name="Path" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
and the new code when you load the window:
SetText("");
var transform = new ScaleTransform(1, 1);
Path.LayoutTransform = transform;
var animationX = new DoubleAnimation(1, 10, new Duration(TimeSpan.FromSeconds(60)));
transform.BeginAnimation(ScaleTransform.ScaleXProperty, animationX);
var animationY = new DoubleAnimation(1, 10, new Duration(TimeSpan.FromSeconds(60)));
transform.BeginAnimation(ScaleTransform.ScaleYProperty, animationY);
and a new method to set the text that is anmiated:
private void SetText(string text)
{
var formatted = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Lucida Console"), 12, Brushes.Black);
Path.Data = formatted.BuildGeometry(new Point(0, 0));
Path.Fill = Brushes.Black;
}
and you have call SetText from your timer event handler.
Note that to avoid horizontal jumpiness, you have to use a fixed-length text string and a constant-width font.
I have a WPF 4 app which I want to enable drag and drop with, currently I have it working with a basic drag and drop implementation, but I have found that it would be much better if, instead of the mouse cursor changing over to represent the move operation, I could use an image underneath my finger.
My drag and drop operation is initiated inside a custom user control, so I will need to insert a visual element into the visual tree and have it follow my finger around, perhaps I should enable the ManipulationDelta event on my main window, check for a boolean then move the item around?
From the mentioned article I was able to simplify a little. Basically what you need to do is subscribe in 3 events:
PreviewMouseLeftButtonDownEvent: The event that runs when you press the left button, you can start the drag action by invoking DragDrop.DoDragDrop
DropEvent: The event that runs when you drop something (control must have AllowDrop set to true in order to accept drops)
GiveFeedbackEvent: The event that runs all the time allowing you to give constant feedback
DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move); the first parameter is the element you are dragging, then the second is the data it is carrying and last the mouse effect.
This method locks the thread. So everything after its call will only execute when you stop dragging.
In the drop event you can retrieve the data you sent on the DoDragDrop call.
The source for my tests are located bellow, and the result is:
Sample drag n' drop (gif)
Full Source
MainWindow.xaml
<Window x:Class="TestWpfPure.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:TestWpfPure"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox x:Name="CardListControl" AllowDrop="True" ItemsSource="{Binding Items}" />
</Grid>
</Window>
Card.xaml
<UserControl x:Class="TestWpfPure.Card"
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"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Border x:Name="CardBorder" BorderBrush="Black" BorderThickness="3" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="246" RenderTransformOrigin="0.5,0.5" CornerRadius="6">
<TextBlock Text="{Binding Text}" TextWrapping="Wrap" FontFamily="Arial" FontSize="14" />
</Border>
</Grid>
</UserControl>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Shapes;
namespace TestWpfPure
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Card> Items { get; set; }
private readonly Style listStyle = null;
private Window _dragdropWindow = null;
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<Card>(new List<Card>
{
new Card { Text = "Task #01" },
new Card { Text = "Task #02" },
new Card { Text = "Task #03" },
new Card { Text = "Task #04" },
new Card { Text = "Task #05" },
});
listStyle = new Style(typeof(ListBoxItem));
listStyle.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
listStyle.Setters.Add(new EventSetter(ListBoxItem.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(CardList_PreviewMouseLeftButtonDown)));
listStyle.Setters.Add(new EventSetter(ListBoxItem.DropEvent, new DragEventHandler(CardList_Drop)));
listStyle.Setters.Add(new EventSetter(ListBoxItem.GiveFeedbackEvent, new GiveFeedbackEventHandler(CardList_GiveFeedback)));
CardListControl.ItemContainerStyle = listStyle;
DataContext = this;
}
protected void CardList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is ListBoxItem)
{
var draggedItem = sender as ListBoxItem;
var card = draggedItem.DataContext as Card;
card.Effect = new DropShadowEffect
{
Color = new Color { A = 50, R = 0, G = 0, B = 0 },
Direction = 320,
ShadowDepth = 0,
Opacity = .75,
};
card.RenderTransform = new RotateTransform(2.0, 300, 200);
draggedItem.IsSelected = true;
// create the visual feedback drag and drop item
CreateDragDropWindow(card);
DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
}
}
protected void CardList_Drop(object sender, DragEventArgs e)
{
var droppedData = e.Data.GetData(typeof(Card)) as Card;
var target = (sender as ListBoxItem).DataContext as Card;
int targetIndex = CardListControl.Items.IndexOf(target);
droppedData.Effect = null;
droppedData.RenderTransform = null;
Items.Remove(droppedData);
Items.Insert(targetIndex, droppedData);
// remove the visual feedback drag and drop item
if (this._dragdropWindow != null)
{
this._dragdropWindow.Close();
this._dragdropWindow = null;
}
}
private void CardList_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
// update the position of the visual feedback item
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
this._dragdropWindow.Left = w32Mouse.X;
this._dragdropWindow.Top = w32Mouse.Y;
}
private void CreateDragDropWindow(Visual dragElement)
{
this._dragdropWindow = new Window();
_dragdropWindow.WindowStyle = WindowStyle.None;
_dragdropWindow.AllowsTransparency = true;
_dragdropWindow.AllowDrop = false;
_dragdropWindow.Background = null;
_dragdropWindow.IsHitTestVisible = false;
_dragdropWindow.SizeToContent = SizeToContent.WidthAndHeight;
_dragdropWindow.Topmost = true;
_dragdropWindow.ShowInTaskbar = false;
Rectangle r = new Rectangle();
r.Width = ((FrameworkElement)dragElement).ActualWidth;
r.Height = ((FrameworkElement)dragElement).ActualHeight;
r.Fill = new VisualBrush(dragElement);
this._dragdropWindow.Content = r;
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
this._dragdropWindow.Left = w32Mouse.X;
this._dragdropWindow.Top = w32Mouse.Y;
this._dragdropWindow.Show();
}
[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;
};
}
}
Card.xaml.cs
using System.ComponentModel;
using System.Windows.Controls;
namespace TestWpfPure
{
/// <summary>
/// Interaction logic for Card.xaml
/// </summary>
public partial class Card : UserControl, INotifyPropertyChanged
{
private string text;
public string Text
{
get
{
return this.text;
}
set
{
this.text = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
public Card()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
There is an example of using a custom drag cursor at Jaime Rodriguez msdn blog. You can handle the GiveFeedback event and change the mouse cursor, but to use a custom Visual the author creates a new Window and updates the position on QueryContinueDrag.