Is a dashed border around a selected text/image element possible in Silverlight? - wpf

I have an image editor I'm developing in silverlight which has multiple text and image elements on one canvas, that are draggable etc. I need feedback for the user to highlight the selected element when it is clicked on by the user and highlight a different element instead if another is clicked. I think I should do this with a dashed border around the element, but I don't know if it's possible.
Below is my code relating to the elements -
Project.cs
namespace ImageEditor.Client.BLL
{
public class Project : INotifyPropertyChanged
{
private int numberOfElements;
#region Properties
private ObservableCollection<FrameworkElement> elements;
public ObservableCollection<FrameworkElement> Elements
{
get { return elements; }
set
{
elements = value;
NotifyPropertyChanged("Elements");
}
}
private FrameworkElement selectedElement;
public FrameworkElement SelectedElement
{
get { return selectedElement; }
set
{
selectedElement = value;
NotifyPropertyChanged("SelectedElement");
}
}
private TextBlock selectedTextElement;
public TextBlock SelectedTextElement
{
get { return selectedTextElement; }
set
{
selectedTextElement = value;
NotifyPropertyChanged("SelectedTextElement");
}
}
private Image selectedImageElement;
public Image SelectedImageElement
{
get { return selectedImageElement; }
set
{
selectedImageElement = value;
NotifyPropertyChanged("SelectedImageElement");
}
}
#endregion
#region Methods
private void AddTextElement(object param)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = "New Text";
textBlock.Foreground = new SolidColorBrush(Colors.Gray);
textBlock.FontSize = 25;
textBlock.FontFamily = new FontFamily("Arial");
textBlock.Cursor = Cursors.Hand;
textBlock.Tag = null;
AddDraggingBehavior(textBlock);
textBlock.MouseLeftButtonUp += element_MouseLeftButtonUp;
this.Elements.Add(textBlock);
numberOfElements++;
this.SelectedElement = textBlock;
this.selectedTextElement = textBlock;
}
private BitmapImage GetImageFromLocalMachine(out bool? success, out string fileName)
{
OpenFileDialog dialog = new OpenFileDialog()
{
Filter = "Image Files (*.bmp;*.jpg;*.gif;*.png;)|*.bmp;*.jpg;*.gif;*.png;",
Multiselect = false
};
success = dialog.ShowDialog();
if (success == true)
{
fileName = dialog.File.Name;
FileStream stream = dialog.File.OpenRead();
byte[] data;
BitmapImage imageSource = new BitmapImage();
using (FileStream fileStream = stream)
{
imageSource.SetSource(fileStream);
data = new byte[fileStream.Length];
fileStream.Read(data, 0, data.Length);
fileStream.Flush();
fileStream.Close();
}
return imageSource;
}
else
{
fileName = string.Empty;
return new BitmapImage();
}
}
private void AddImageElement(object param)
{
bool? gotImage;
string fileName;
BitmapImage imageSource = GetImageFromLocalMachine(out gotImage, out fileName);
if (gotImage == true)
{
Image image = new Image();
image.Name = fileName;
image.Source = imageSource;
image.Height = imageSource.PixelHeight;
image.Width = imageSource.PixelWidth;
image.MaxHeight = imageSource.PixelHeight;
image.MaxWidth = imageSource.PixelWidth;
image.Cursor = Cursors.Hand;
image.Tag = null;
AddDraggingBehavior(image);
image.MouseLeftButtonUp += element_MouseLeftButtonUp;
this.Elements.Add(image);
numberOfElements++;
this.SelectedElement = image;
this.SelectedImageElement = image;
}
}
private void OrderElements()
{
var elList = (from element in this.Elements
orderby element.GetValue(Canvas.ZIndexProperty)
select element).ToList<FrameworkElement>();
for (int i = 0; i < elList.Count; i++)
{
FrameworkElement fe = elList[i];
fe.SetValue(Canvas.ZIndexProperty, i);
}
this.Elements = new ObservableCollection<FrameworkElement>(elList);
}
public void element_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.SelectedElement = sender as FrameworkElement;
if (sender is TextBlock)
{
this.SelectedTextElement = sender as TextBlock;
FadeOut(this.SelectedTextElement);
}
else if (sender is Image)
{
this.SelectedImageElement = sender as Image;
FadeOut(this.SelectedImageElement);
}
}
#endregion
More than needed there but you get a good idea of how it all works from that. How might I go about it? I'm still pretty new to silverlight
Edit:
This is my start attempt at a DashBorder Method, wherein I'm trying to make a rectangle the same dimensions as the selected element which will go around the element
public static void DashBorder(FrameworkElement element)
{
Rectangle rect = new Rectangle();
rect.Stroke = new SolidColorBrush(Colors.Black);
rect.Width=element.Width;
rect.Height=element.Height;
rect.StrokeDashArray = new DoubleCollection() { 2, 2 };
}
It appears to do nothing and isn't what I want to do anyway. Is there no way to make a dash border on a FrameworkElement directly?

I don't know how, but google does.
You can use the StrokeDashArray to achieve the desired effect,
example:
<Rectangle Canvas.Left="10" Canvas.Top="10" Width="100" Height="100"
Stroke="Black" StrokeDashArray="10, 2"/>
The first number in StrokeDashArray is the length of the dash, the
second number is the length of the gap. You can repeat the dash gap
pairs to generate different patterns.
Edit:
To do this in code create a rectangle and set it's StrokeDashArray property like this (code untested):
Rectangle rect = new Rectangle();
rect.StrokeThickness = 1;
double[] dashArray = new double[2];
dashArray[0] = 2;
dashArray[1] = 4;
rect.StrokeDashArray = dashArray;

Related

WPF DataGrid - Creating a new custom Column

I am trying to create my own checkbox column (replacing the default one), in order to move to more complex data columns later-on, and I have the following code:
public class MyCheckBoxColumn : DataGridBoundColumn
{
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var cb = new CheckBox();
var bb = this.Binding as Binding;
var b = new Binding { Path = bb.Path, Source = cell.DataContext };
cb.SetBinding(ToggleButton.IsCheckedProperty, b);
return cb;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
var cb = new CheckBox();
var bb = this.Binding as Binding;
var b = new Binding { Path = bb.Path, Source = ToggleButton.IsCheckedProperty };
cb.SetBinding(ToggleButton.IsCheckedProperty, b);
return cb;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var cb = editingElement as CheckBox;
return cb.IsChecked;
}
protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
{
var cb = editingElement as CheckBox;
if (cb != null) cb.IsChecked = (bool)uneditedValue;
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
var cb = editingElement as CheckBox;
BindingExpression binding = editingElement.GetBindingExpression(ToggleButton.IsCheckedProperty);
if (binding != null) binding.UpdateSource();
return true;// base.CommitCellEdit(editingElement);
}
}
And my custom DataGrid:
public class MyDataGrid : DataGrid
{
protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
{
try
{
var type = e.PropertyType;
if (type == typeof(bool))
{
var col = new MyCheckBoxColumn();
col.Binding = new Binding(e.PropertyName) {Mode = BindingMode.TwoWay};
e.Column = col;
}
else
{
base.OnAutoGeneratingColumn(e);
}
var propDescr = e.PropertyDescriptor as System.ComponentModel.PropertyDescriptor;
e.Column.Header = propDescr.Description;
}
catch (Exception ex)
{
Utils.ReportException(ex);
}
}
}
Now, everything seems nice except for two things:
It seems that the only used method in in MyCheckBoxColumn is the GenerateElement(). All the other methods are not used. I have put breakpoints in them and they never get hit...
I use an ObservableCollection as a data source and, while the rest of the columns notify me when they get changed, this one doesn't.
The odd thing is that the bool value gets changed when you check/uncheck the checkbox, but without notification and without passing through CommitCellEdit().
Does anyone know what is going wrong here?
EDIT :
It seems that if I return a TextBlock from inside GenerateElement() it makes the other methods to be called (the notification problem doesn't get fixed though). But why doesn't this work with with CheckBoxes? How does the default check box column work???
OK. Here is the complete code for a custom CheckBox column. It seems that, in order to have a control like a checkbox as a display (not editing) element in a DataGrid you have to make it hit-test-invisible. Or you can simply use a TextBlock to display some character that resembles a checkmark:
public class MyCheckBoxColumn : DataGridBoundColumn
{
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var cb = new CheckBox() { IsHitTestVisible = false, HorizontalAlignment = HorizontalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center };
var bb = this.Binding as Binding;
var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay };
cb.SetBinding(ToggleButton.IsCheckedProperty, b);
return cb;
// var cb = new TextBlock() { TextAlignment = TextAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center };
// var bb = this.Binding as Binding;
// var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay, Converter = new MyBoolToMarkConverter() };
// cb.SetBinding(TextBlock.TextProperty, b);
// return cb;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
var cb = new CheckBox() { HorizontalAlignment = HorizontalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center };
var bb = this.Binding as Binding;
var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay };
cb.SetBinding(ToggleButton.IsCheckedProperty, b);
return cb;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var cb = editingElement as CheckBox;
if (cb != null) return cb.IsChecked;
return false;
}
protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
{
var cb = editingElement as CheckBox;
if (cb != null) cb.IsChecked = (bool)uneditedValue;
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
// The following 2 lines seem to help when sometimes the commit doesn't happen (for unknown to me reasons).
//var cb = editingElement as CheckBox;
//cb.IsChecked = cb.IsChecked;
BindingExpression binding = editingElement.GetBindingExpression(ToggleButton.IsCheckedProperty);
if (binding != null) binding.UpdateSource();
return true;// base.CommitCellEdit(editingElement);
}
}
//--------------------------------------------------------------------------------------------
public class MyBoolToMarkConverter : IValueConverter
{
const string cTick = "■";
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(bool)) return "";
bool val = (bool)value;
return val ? cTick : "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(string)) return false;
string val = (string)value;
return val == cTick;
}
}
//--------------------------------------------------------------------------------------------

Transparency in animator control

I'm writing a user control for animation.
Its uses an internal ImageList to store the animation images and paint one after the other in a loop.
This is the whole code:
public partial class Animator : UserControl
{
public event EventHandler OnLoopElapsed = delegate { };
private ImageList imageList = new ImageList();
private Timer timer;
private bool looping = true;
private int index;
private Image newImage;
private Image oldImage;
public Animator()
{
InitializeComponent();
base.DoubleBuffered = true;
timer = new Timer();
timer.Tick += timer_Tick;
timer.Interval = 50;
}
public bool Animate
{
get { return timer.Enabled; }
set
{
index = 0;
timer.Enabled = value;
}
}
public int CurrentIndex
{
get { return index; }
set { index = value; }
}
public ImageList ImageList
{
set
{
imageList = value;
Invalidate();
index = 0;
}
get { return imageList; }
}
public bool Looping
{
get { return looping; }
set { looping = value; }
}
public int Interval
{
get { return timer.Interval; }
set { timer.Interval = value; }
}
private void timer_Tick(object sender, EventArgs e)
{
if (imageList.Images.Count == 0)
return;
Invalidate(true);
index++;
if (index >= imageList.Images.Count)
{
if (looping)
index = 0;
else
timer.Stop();
OnLoopElapsed(this, EventArgs.Empty);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
if (oldImage != null)
e.Graphics.DrawImage(oldImage, ClientRectangle);
else
e.Graphics.Clear(BackColor);
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
if (imageList.Images.Count > 0)
{
newImage = imageList.Images[index];
g.DrawImage(newImage, ClientRectangle);
oldImage = newImage;
}
else
{
e.Graphics.Clear(BackColor);
}
}
}
The animation seems very nice and smooth,
but the problem is that its surrounding rectangle is painted black.
What am I missing here?
I've seen very smooth transparent animation done here in WPF,
I've placed some label behind it and they are seen thru the rotating wheel as I hoped.
But I don't know WPF well enough to build such a control in WPF.
Any idea or WPF sample code will be appreciated.
This was solved by removing this line from the constructor:
base.DoubleBuffered = true;
Now the control is fully transparent, even while changing its images.

DataBinding Image.Source to a PhotoChooserTask result not working

I have the following class:
public class Sticky : INotifyPropertyChanged {
// ... some members
private BitmapImage _frontPic;
[DataMember]
public BitmapImage FrontPic {
get {
return _frontPic;
}
set {
_frontPic = value;
Changed("FrontPic");
Changed("FrontBrush");
}
}
}
I'm trying to databind it to this XAML:
<Image Width="173" Height="173" Source="{Binding FrontPic}" />
after launching a PhotoChooserTask with this code in my PhoneApplicationPage:
public Sticky Sticky { get; set; }
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) {
Sticky = new Sticky();
DataContext = Sticky;
}
private void ShowFrontPicPicker(object sender, RoutedEventArgs e) {
var t = new PhotoChooserTask();
t.PixelHeight = 173;
t.PixelWidth = 173;
t.ShowCamera = true;
t.Completed += (s, ev) => {
if (ev.TaskResult == TaskResult.OK) {
var img = new BitmapImage();
img.SetSource(ev.ChosenPhoto);
Sticky.FrontPic = img;
}
};
t.Show();
}
However, my image remains blank. If I assign the Image.Source property directly to the Image without databinding, everything works. Databinding other properties works, it's just the image that seems to be the problem. How can I make the DataBinding on the image work?
Found the problem! The completed callback for PhotoChooserTask does not excecute in the UI thread, so a call to Dispatcher.BeginInvoke must be added:
t.Completed += (s, ev) => Dispatcher.BeginInvoke(() => {
// do stuff...
});

Why is the ListView with an ImageList very slow? (re: 1000+ thumbnails)

I'm trying to use a ListView component to show around 1,000 image thumbnails and I'm having some performance problems.
First I create an ImageList containing my 1,000 images. This is lightning fast and takes under a second.
However, once I assign the ImageList to my ListView, it takes around 10+ seconds.
Example:
ImageList _imgList = GetMyImageList(); // takes under 1 second
ListView _lstView = new ListView();
lstView.LargeImageList = _imgList; // takes 10+ seconds
Is there anything I can do to increase performance? My ImageList contains images that are already resized into thumbnail size (197x256 pixels) so that's not the problem... (and creating my ImageList only takes 1 second at the most).
Does the data in your list view change frequently? Do you load new image lists frequently?
I tried your scenario and got a few seconds of loading time (since I'm generating random images) but very fast refresh times when changing list view [View] modes as well as scrolling.
Here is the sample code. Try it out and let me know how it works.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace WindowsFormsApplication10
{
public partial class FormListView:
System.Windows.Forms.Form
{
public FormListView ()
{
string [] names = null;
this.InitializeComponent();
names = Enum.GetNames(typeof(View));
for (int i=0; i < names.Length; i++)
{
this.comboBox1.Items.Add(names [i]);
if (names [i] == this.ListView.View.ToString())
this.comboBox1.SelectedIndex = i;
}
}
private void comboBox1_SelectedIndexChanged (object sender, EventArgs e)
{
this.ListView.View = (View) Enum.Parse(typeof(View), this.comboBox1.SelectedItem.ToString());
this.ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
}
private void ButtonLoadImages_Click (object sender, System.EventArgs e)
{
Image image;
Stopwatch watch;
this.Enabled = false;
this.Cursor = Cursors.WaitCursor;
this.ListView.SmallImageList = null;
this.ListView.LargeImageList = null;
this.ListView.StateImageList = null;
while (this.ImageList.Images.Count > 0)
{
this.ImageList.Images [0].Dispose();
this.ImageList.Images.RemoveAt(0);
}
this.ImageList.ImageSize = new System.Drawing.Size(256, 256);
watch = Stopwatch.StartNew();
for (int i=0; i < 1000; i++)
{
image = new Bitmap(this.ImageList.ImageSize.Width, this.ImageList.ImageSize.Height);
using (Graphics graphics = Graphics.FromImage(image))
{
graphics.Clear(Color.White);
graphics.DrawRectangle(Pens.Red, 10, 10, this.ImageList.ImageSize.Width - 20, this.ImageList.ImageSize.Height - 20);
graphics.DrawString(i.ToString(), this.Font, Brushes.Blue, 20, 20);
}
this.ImageList.Images.Add(image);
}
watch.Stop();
this.ListView.SmallImageList = this.ImageList;
this.ListView.LargeImageList = this.ImageList;
this.ListView.StateImageList = this.ImageList;
this.Text = watch.Elapsed.TotalSeconds.ToString();
this.Cursor = Cursors.Default;
this.Enabled = true;
}
private void ButtonLoadItems_Click (object sender, System.EventArgs e)
{
Stopwatch watch;
ListViewItem item;
this.Enabled = false;
this.Cursor = Cursors.WaitCursor;
this.ListView.Items.Clear();
this.ListView.Columns.Clear();
this.ListView.Columns.Add("Id", "Id");
this.ListView.Columns.Add("Name", "Name");
this.ListView.SmallImageList = null;
this.ListView.LargeImageList = null;
this.ListView.StateImageList = null;
this.ListView.BeginUpdate();
watch = Stopwatch.StartNew();
for (int i=0; i < 1000; i++)
{
item = new ListViewItem();
item.ImageIndex = i;
item.Text = i.ToString();
item.SubItems.Add("qwerty");
this.ListView.Items.Add(item);
}
this.ListView.EndUpdate();
this.ListView.SmallImageList = this.ImageList;
this.ListView.LargeImageList = this.ImageList;
this.ListView.StateImageList = this.ImageList;
this.ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
watch.Stop();
this.Text = watch.Elapsed.TotalSeconds.ToString();
this.Cursor = Cursors.Default;
this.Enabled = true;
}
}
}

Allow a user to create a line series on a wpf chart by clicking on the chart

I have a WPF chart currently displaying an Area series. I need to allow a user the ability to click on the chart and add a new point for a Line series. The problem I'm having is I can't seem to find a way to convert the point from a MouseButtonEventArgs to a LineDataPoint.
private void chtPowerFlowMap_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point p = e.GetPosition(chtPowerFlowMap);
points.Add(p); //Issue here is this will return a point in screen coordinates and not in chart coordinates
ls.ItemsSource = points; //ls is an existing lineseries and points is a List<Point> object
chtPowerFlowMap.Series.Add(ls);
}
Thanks in advance.
Here is a complete solution. You can click anywhere on the chart and there will be a new point at that palce.
MainWindows.xaml
<chart:Chart x:Name="chart" MouseLeftButtonDown="Chart_MouseLeftButtonDown">
<chart:LineSeries ItemsSource="{Binding}" x:Name="lineSeries"
DependentValuePath="Value"
IndependentValuePath="Date"/>
</chart:Chart>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private ObservableCollection<Item> items;
public MainWindow()
{
InitializeComponent();
Random rd = new Random();
items = new ObservableCollection<Item>(
Enumerable.Range(0, 10)
.Select(i => new Item
{
Date = DateTime.Now.AddMonths(i - 10),
Value = rd.Next(10,50)
}));
this.DataContext = items;
}
public class Item
{
public DateTime Date { get; set; }
public double Value { get; set; }
}
private void Chart_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var p = Mouse.GetPosition(this.lineSeries);
//ranges in the real values
var left = items.Min(i => i.Date);
var right = items.Max(i => i.Date);
var top = items.Max(i => i.Value);
var bottom = items.Min(i => i.Value);
var hRange = right - left;
var vRange = top - bottom;
//ranges in the pixels
var width = this.lineSeries.ActualWidth;
var height = this.lineSeries.ActualHeight;
//from the pixels to the real value
var currentX = left + TimeSpan.FromTicks((long)(hRange.Ticks * p.X / width));
var currentY = top - vRange * p.Y / height;
this.items.Add(new Item { Date = currentX, Value = currentY });
}
}

Resources