Issues with adding custom control to winforms tabcontrol - winforms

I am having trouble adding a custom zoom-able an pan-able picture box control to a tabcontrol.tabpage dynamically at runtime. I have tried a lot and was wondering if any of you smart fellas might have some advice for a poor noob like myself... here is some code...
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace TAQTv4
{
public class ZoomPanPicBox : ScrollableControl
{
private Image _image;
//Double buffer the control
public ZoomPanPicBox()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
this.AutoScroll = true;
this.Image = null;
this.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
this.Zoom = 1f;
}
//New
[Category("Appearance"), Description("The image to be displayed")]
public Image Image
{
get { return _image; }
set
{
_image = value;
UpdateScaleFactor();
Invalidate();
}
}
private float _zoom = 1f;
[Category("Appearance"), Description("The zoom factor. Less than 1 to reduce. More than 1 to magnify.")]
public float Zoom
{
get { return _zoom; }
set
{
if (value < 0 || value < 1E-05)
{
value = 1E-05f;
}
_zoom = value;
UpdateScaleFactor();
Invalidate();
}
}
private void UpdateScaleFactor()
{
if (_image == null)
{
this.AutoScrollMargin = this.Size;
}
else
{
this.AutoScrollMinSize = new Size(Convert.ToInt32(this._image.Width * _zoom + 0.5f), Convert.ToInt32(this._image.Height * _zoom + 0.5f));
}
}
//UpdateScaleFactor
private InterpolationMode _interpolationMode = InterpolationMode.High;
[Category("Appearance"), Description("The interpolation mode used to smooth the drawing")]
public InterpolationMode InterpolationMode
{
get { return _interpolationMode; }
set { _interpolationMode = value; }
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
//OnPaintBackground
protected override void OnPaint(PaintEventArgs e)
{
//if no image, don't bother. I tried check for IsNothing(_image) but this test wasn't detecting a no-image.
if (_image == null)
{
base.OnPaintBackground(e);
return;
}
//Added because the first test sometimes failed
try
{
int H = _image.Height;
//Throws an exception if image is nothing.
}
catch (Exception ex)
{
base.OnPaintBackground(e);
return;
}
//Set up a zoom matrix
Matrix mx = new Matrix(_zoom, 0, 0, _zoom, 0, 0);
mx.Translate(this.AutoScrollPosition.X / _zoom, this.AutoScrollPosition.Y / _zoom);
e.Graphics.Transform = mx;
e.Graphics.InterpolationMode = _interpolationMode;
e.Graphics.DrawImage(_image, new Rectangle(0, 0, this._image.Width, this._image.Height), 0, 0, _image.Width, _image.Height, GraphicsUnit.Pixel);
base.OnPaint(e);
}
//OnPaint
}
}
//ZoomPicBox
Now this seems to work fine while using the designer... but when trying to add images and controls at runtime the tabs instantiate fine but the zoomPicBox control does nothing so it would seem... This is how I am using it....
public void loadImagesToTabControl()
{
int i = 0;
foreach (Bitmap bitmap in intDwg.getBitmaps())
{
//ToDo add pic boxes and tabs and bitmaps to tabcontrol1
TAQTv4.ZoomPanPicBox picBox = new TAQTv4.ZoomPanPicBox();
picBox.Image = bitmap;
picBox.Anchor = AnchorStyles.Top;
picBox.Anchor = AnchorStyles.Bottom;
picBox.Anchor = AnchorStyles.Left;
picBox.Anchor = AnchorStyles.Right;
picBox.AutoScroll = true;
picBox.CausesValidation = true;
picBox.Visible = true;
picBox.Zoom = 1;
picBox.BackgroundImageLayout = ImageLayout.Tile;
picBox.Location = new System.Drawing.Point(0, 0);
picBox.TabStop = true;
picBox.Enabled = true;
picBox.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
picBox.CreateControl();
string title = "Pg " + (tabControl1.TabCount + 1).ToString();
TabPage myTabPage = new TabPage(title);
tabControl1.TabPages.Add(myTabPage);
tabControl1.TabPages[i].Controls.Add(picBox);
i++;
/* Possible pictureBox Implementation...
string title = "Pg " + (tabControl1.TabCount + 1).ToString();
TabPage myTabPage = new TabPage(title);
tabControl1.TabPages.Add(myTabPage);
PictureBox picbox = new PictureBox();
picbox.Anchor = AnchorStyles.Top;
picbox.Anchor = AnchorStyles.Bottom;
picbox.Anchor = AnchorStyles.Left;
picbox.Anchor = AnchorStyles.Right;
picbox.Image = bitmap;
picbox.Height = 800;
picbox.Width = 1300;
tabControl1.TabPages[i].Controls.Add(picbox);
i++;
*/
}
}
}
And as a last note... the pictureBox implementation worked fine as well so I know I am pulling my images from disk fine in the deserialization method of my intDwg class. Any thoughts would be much appreciated! Thanks in advance!
UPDATE:
I got the control to load pictures by setting backgroundimage to bitmap instead of picBox.Image.... FRUSTRATING .... but it seems that the way I have it set up the image is not anchored correctly ... trying to improve this and work it out now... any tips and tricks would be just awesome! Thanks!
UPDATE:
A Screen shot... as you can see the tab pages load correctly and one for each bitmap in my collection, yet the custom zoomPanPicBox control does not seem to want to display! See Bellow:
ahh .... seems I don't have rep to post pics.... ... alright how about...
https://www.dropbox.com/s/ogj5jlcce831n3p/scrst.png?v=0mcns
...
UPDATE AGAIN GOT IT THANKS All was missing setting the size as you had mentioned using the following: picBox.SetBounds(0, 0, 300, 300);
:D:D:D:D:D:D:)

Also, instead of using a counter:
TabPage myTabPage = new TabPage(title);
tabControl1.TabPages.Add(myTabPage);
tabControl1.TabPages[i].Controls.Add(picBox);
i++;
Just use your "myTabPage" reference:
TabPage myTabPage = new TabPage(title);
myTabPage.Controls.Add(picBox);
tabControl1.TabPages.Add(myTabPage);

Related

Task cancellation: while loop not exited properly when ThrowIfCancellationRequested() is called

I've developed a small project (using MVVM) with functionality to upload a file to a FTP-server.
The user can view the uploading progress: percentage completed is shown to the user by vm.Busycontent, which is a property in my viewmodel bound to a UI element in my view.
Here is the code for reading the file and uploading via FTP (which is part of the Task vm.FtpUploadTask)
using (FileStream inputStream = File.OpenRead(file))
{
using (outputStream = request.GetRequestStream())
{
var buffer = new byte[1024 * 1024];
int totalReadBytesCount = 0;
int readBytesCount;
while ((readBytesCount = inputStream.Read(buffer, 0, buffer.Length)) > 0 && (!vm.Token.IsCancellationRequested))
{
vm.Token.ThrowIfCancellationRequested();
outputStream.Write(buffer, 0, readBytesCount);
totalReadBytesCount += readBytesCount;
var progress = totalReadBytesCount * 100.0 / inputStream.Length;
vm.BusyContent = ((int)progress).ToString();
}
}
}
MainWindow.xaml
I am using WPF extended toolkit BusyIndicator
<xctk:BusyIndicator IsBusy='{Binding IsBusy}'>
<xctk:BusyIndicator.BusyContentTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text='{Binding Path=DataContext.BusyContent,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}' />
</StackPanel>
</DataTemplate>
</xctk:BusyIndicator.BusyContentTemplate>
</xctk:BusyIndicator>
UploadToFTPCommand.cs
try
{
vm.FtpUploadTask = new Task(() => FTPUpload(file), vm.Token);
vm.FtpUploadTask.Start();
vm.FtpUploadTask.Wait(vm.Token);
vm.BusyContent = "upload done!";
}
catch (OperationCanceledException)
{
vm.BusyContent = "Canceled";
}
CancelCommand.cs
public class CancelCommand : ICommand
{
public void Execute(object parameter)
{
vm.TokenSource.Cancel();
}
}
The cancel function works but sometimes vm.Busycontent equals to
((int)progress).ToString()
canceled in uploadftpcommand
When pressing the Cancel button, the while loop should be exited and the user should only see the message in the catch (OperationCanceledException).
Any ideas how to solve this?
Notes
I am using .NET 4.0
This program is a part of a larger project, which includes multipe Tasks that should be executed in a synchronous manner. That's why I am using Task.Start() and Task.Wait() methods.
Edit
Problem still remains
using (FileStream inputStream = File.OpenRead(file))
{
using (outputStream = request.GetRequestStream())
{
var buffer = new byte[1024 * 1024];
int totalReadBytesCount = 0;
int readBytesCount;
while ((readBytesCount = inputStream.Read(buffer, 0, buffer.Length)) > 0)
{
if (vm.Token.IsCancellationRequested)
{
break;
}
outputStream.Write(buffer, 0, readBytesCount);
totalReadBytesCount += readBytesCount;
var progress = totalReadBytesCount * 100.0 / inputStream.Length;
vm.BusyContent = ((int)progress).ToString();
}
if (vm.Token.IsCancellationRequested)
{
inputStream.Close();
outputStream.Close();
vm.Token.ThrowIfCancellationRequested();
}
}
}
You may have an issue relating to exactly how you've set up your code. Without going through every line of it, it's difficult to tell. But more to the point:
When pressing the Cancel button, the while loop should be exited and the user should only see the message in the catch (OperationCanceledException).
This shows exactly how to exit your while loop and throw an exception you can catch. Note the test asserts TaskCanceledException but you are right in just catching OperationCanceledException.
using System.Threading.Tasks;
using System.IO;
using System.Threading;
using NUnit.Framework;
namespace CancellationTests {
[TestFixture]
public class WhileCancellation {
[Test]
public void While_Loop_Is_Canceled_When_Cancel_Is_Requested() {
var inputFile = new FileInfo("somebigfile.txt");
var outputFile = new FileInfo("outputFile.txt");
var cts = new CancellationTokenSource();
cts.Cancel();
Assert.ThrowsAsync<TaskCanceledException>(() => TheWhileLoop(inputFile, outputFile, cts.Token));
}
private async Task TheWhileLoop(FileInfo inputFile, FileInfo outputFile, CancellationToken token) {
using (var inputStream = inputFile.OpenRead())
using (var outputStream = outputFile.OpenWrite()) {
var buffer = new byte[1024 * 1024];
var totalReadBytesCount = 0;
var readBytesCount = 0;
while ((readBytesCount = await inputStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0) {
await outputStream.WriteAsync(buffer, 0, readBytesCount, token);
token.ThrowIfCancellationRequested();
totalReadBytesCount += readBytesCount;
var progress = totalReadBytesCount * 100.0 / inputStream.Length;
vm.BusyContent = ((int)progress).ToString();
}
}
}
private static class vm {
public static string BusyContent { get; set; }
}
}
}
Hopefully this gets you on the right track.

Silverlight Pixel Perfect Collsion Detection

I am working on a project for my Uni, and I am currently stuck on a Pixel Perfect Collision Detection from this website http://www.andybeaulieu.com/Home/tabid/67/EntryID/160/Default.aspx. I have downloded example project that is using this collsion detection and it is working fine even with my own pictures. I have done the same thing in my project and it is not working. Here is the link to my app: https://www.cubby.com/pl/LostInTheMath.zip/_dd23e2c827604c068a3fe63ff42d22b2 could anyone tell me whats wrong with it? Thank you.
Here is the main code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
namespace LostInTheMath
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
private void UserCntrl_MouseMove(object sender, MouseEventArgs e)
{
Point pt = e.GetPosition(cnvHitTest);
Monkey.SetValue(Canvas.LeftProperty, pt.X);
Monkey.SetValue(Canvas.TopProperty, pt.Y);
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
Image MonkeyShell = Monkey.FindName("imgMonkey") as Image;
Image Mazer = Maze.FindName("imgMaze") as Image;
if (CheckCollision(Monkey, MonkeyShell, Maze, Mazer))
{
Monkey.Width = 1000;
txtStatus.Text = "Collision with XAML Element!";
return;
}
txtStatus.Text = string.Empty;
}
private bool CheckCollision(FrameworkElement control1, FrameworkElement controlElem1, FrameworkElement control2, FrameworkElement controlElem2)
{
// first see if sprite rectangles collide
Rect rect1 = UserControlBounds(control1);
Rect rect2 = UserControlBounds(control2);
rect1.Intersect(rect2);
if (rect1 == Rect.Empty)
{
// no collision - GET OUT!
return false;
}
else
{
bool bCollision = false;
Point ptCheck = new Point();
// NOTE that creating the writeablebitmap is a bit intense
// so we will do this once and store results in Tag property
// in a real game, you might abstract this into a Sprite class.
if (controlElem1 is Image)
controlElem1.Tag = GetWriteableBitmap(control1);
if (controlElem2 is Image)
controlElem2.Tag = GetWriteableBitmap(control2);
// now we do a more accurate pixel hit test
for (int x = Convert.ToInt32(rect1.X); x < Convert.ToInt32(rect1.X + rect1.Width); x++)
{
for (int y = Convert.ToInt32(rect1.Y); y < Convert.ToInt32(rect1.Y + rect1.Height); y++)
{
ptCheck.X = x;
ptCheck.Y = y;
if (CheckCollisionPoint(ptCheck, control1, controlElem1))
if (CheckCollisionPoint(ptCheck, control2, controlElem2))
{
bCollision = true;
break;
}
}
if (bCollision) break;
}
return bCollision;
}
}
public bool CheckCollisionPoint(Point pt, FrameworkElement control, FrameworkElement controlElem)
{
if (controlElem is Image)
{
// NOTE that we saved the WB in the Tag object for performance.
// in a real app, you might abstract this in your sprite class.
WriteableBitmap wb = controlElem.Tag as WriteableBitmap;
int width = wb.PixelWidth;
int height = wb.PixelHeight;
double offSetX = Convert.ToDouble(control.GetValue(Canvas.LeftProperty));
double offSetY = Convert.ToDouble(control.GetValue(Canvas.TopProperty));
pt.X = pt.X - offSetX;
pt.Y = pt.Y - offSetY;
int offset = Convert.ToInt32((width * pt.Y) + pt.X);
return (wb.Pixels[offset] != 0);
}
else
{
List<UIElement> hits = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(pt, controlElem) as List<UIElement>;
return (hits.Contains(controlElem));
}
}
private WriteableBitmap GetWriteableBitmap(FrameworkElement control)
{
WriteableBitmap wb = new WriteableBitmap((int)control.Width, (int)control.Height); ;
wb.Render(control, new TranslateTransform());
wb.Invalidate();
return wb;
}
public Rect UserControlBounds(FrameworkElement control)
{
Point ptTopLeft = new Point(Convert.ToDouble(control.GetValue(Canvas.LeftProperty)), Convert.ToDouble(control.GetValue(Canvas.TopProperty)));
Point ptBottomRight = new Point(Convert.ToDouble(control.GetValue(Canvas.LeftProperty)) + control.Width, Convert.ToDouble(control.GetValue(Canvas.TopProperty)) + control.Height);
return new Rect(ptTopLeft, ptBottomRight);
}
}
}

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

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;

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;
}
}
}

WPF 3D - How can I save and load a Camera view?

I have a WPF 3D scene where I can pan, rotate and zoom using the TrackballDecorator from the 3DTools library. I would like to save the camera settings (transformation) and be able to re-apply them when the application restarts the next time (so the view is restored).
I tried to save each individual value of the Camera:
private void SaveCameraSettings()
{
var d = Properties.Settings.Default;
d.CameraPositionX = camera.Position.X;
d.CameraPositionY = camera.Position.Y;
...
d.Save();
}
This doesn't work, I guess because those settings are not updated according to the transformations applied to the camera (I always get the initial values set in xaml).
I checked the the Transformation3D class but couldn't find any way to set its value...
The problem is what values do I need to get from the PerspectiveCamera in order to be able to restore it the way it was when I closed my application the last time. The camera is set to a default position (in Xaml), then a transformation is applied to this camera by the TrackBallDecorator. How can I save this transformation (what values to store)? And how can I re-apply them at a later time?
This is going to be a bit long, so bear with me...
1st, you need to modify the 3DTools library so you can apply a transformation to the TrackballDecorator as follow:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Input;
namespace _3DTools
{
public class TrackballDecorator : Viewport3DDecorator
{
#region Private Members
private Point m_PreviousPosition2D;
private Vector3D m_PreviousPosition3D = new Vector3D(0, 0, 1);
private Transform3DGroup m_Transform;
private ScaleTransform3D m_Scale = new ScaleTransform3D();
private AxisAngleRotation3D m_Rotation = new AxisAngleRotation3D();
private TranslateTransform3D m_Translate = new TranslateTransform3D();
private readonly Border m_EventSource;
#endregion
#region Constructor
public TrackballDecorator()
{
TranslateScale = 10;
ZoomScale = 1;
RotateScale = 1;
// the transform that will be applied to the viewport 3d's camera
m_Transform = new Transform3DGroup();
m_Transform.Children.Add(m_Scale);
m_Transform.Children.Add(new RotateTransform3D(m_Rotation));
m_Transform.Children.Add(m_Translate);
// used so that we always get events while activity occurs within
// the viewport3D
m_EventSource = new Border { Background = Brushes.Transparent };
PreViewportChildren.Add(m_EventSource);
}
#endregion
#region Properties
/// <summary>
/// A transform to move the camera or scene to the trackball's
/// current orientation and scale.
/// </summary>
public Transform3DGroup Transform
{
get { return m_Transform; }
set
{
m_Transform = value;
m_Scale = m_Transform.GetScaleTransform3D();
m_Translate = m_Transform.GetTranslateTransform3D();
m_Rotation = m_Transform.GetRotateTransform3D().Rotation as AxisAngleRotation3D;
ApplyTransform();
}
}
public double TranslateScale { get; set; }
public double RotateScale { get; set; }
public double ZoomScale { get; set; }
#endregion
#region Event Handling
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
m_PreviousPosition2D = e.GetPosition(this);
m_PreviousPosition3D = ProjectToTrackball(ActualWidth,
ActualHeight,
m_PreviousPosition2D);
if (Mouse.Captured == null)
{
Mouse.Capture(this, CaptureMode.Element);
}
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
if (IsMouseCaptured)
{
Mouse.Capture(this, CaptureMode.None);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (IsMouseCaptured)
{
Point currentPosition = e.GetPosition(this);
// avoid any zero axis conditions
if (currentPosition == m_PreviousPosition2D) return;
// Prefer tracking to zooming if both buttons are pressed.
if (e.LeftButton == MouseButtonState.Pressed)
{
Track(currentPosition);
}
else if (e.RightButton == MouseButtonState.Pressed)
{
Zoom(currentPosition);
}
else if (e.MiddleButton == MouseButtonState.Pressed)
{
Translate(currentPosition);
}
m_PreviousPosition2D = currentPosition;
ApplyTransform();
}
}
private void ApplyTransform()
{
Viewport3D viewport3D = Viewport3D;
if (viewport3D != null)
{
if (viewport3D.Camera != null)
{
if (viewport3D.Camera.IsFrozen)
{
viewport3D.Camera = viewport3D.Camera.Clone();
}
if (viewport3D.Camera.Transform != m_Transform)
{
viewport3D.Camera.Transform = m_Transform;
}
}
}
}
#endregion Event Handling
private void Track(Point currentPosition)
{
var currentPosition3D = ProjectToTrackball(ActualWidth, ActualHeight, currentPosition);
var axis = Vector3D.CrossProduct(m_PreviousPosition3D, currentPosition3D);
var angle = Vector3D.AngleBetween(m_PreviousPosition3D, currentPosition3D);
// quaterion will throw if this happens - sometimes we can get 3D positions that
// are very similar, so we avoid the throw by doing this check and just ignoring
// the event
if (axis.Length == 0) return;
var delta = new Quaternion(axis, -angle);
// Get the current orientantion from the RotateTransform3D
var r = m_Rotation;
var q = new Quaternion(m_Rotation.Axis, m_Rotation.Angle);
// Compose the delta with the previous orientation
q *= delta;
// Write the new orientation back to the Rotation3D
m_Rotation.Axis = q.Axis;
m_Rotation.Angle = q.Angle;
m_PreviousPosition3D = currentPosition3D;
}
private static Vector3D ProjectToTrackball(double width, double height, Point point)
{
var x = point.X / (width / 2); // Scale so bounds map to [0,0] - [2,2]
var y = point.Y / (height / 2);
x = x - 1; // Translate 0,0 to the center
y = 1 - y; // Flip so +Y is up instead of down
var z2 = 1 - x * x - y * y; // z^2 = 1 - x^2 - y^2
var z = z2 > 0 ? Math.Sqrt(z2) : 0;
return new Vector3D(x, y, z);
}
private void Zoom(Point currentPosition)
{
var yDelta = currentPosition.Y - m_PreviousPosition2D.Y;
var scale = Math.Exp(yDelta / 100) / ZoomScale; // e^(yDelta/100) is fairly arbitrary.
m_Scale.ScaleX *= scale;
m_Scale.ScaleY *= scale;
m_Scale.ScaleZ *= scale;
}
private void Translate(Point currentPosition)
{
// Calculate the panning vector from screen(the vector component of the Quaternion
// the division of the X and Y components scales the vector to the mouse movement
var qV = new Quaternion(((m_PreviousPosition2D.X - currentPosition.X) / TranslateScale),
((currentPosition.Y - m_PreviousPosition2D.Y) / TranslateScale), 0, 0);
// Get the current orientantion from the RotateTransform3D
var q = new Quaternion(m_Rotation.Axis, m_Rotation.Angle);
var qC = q;
qC.Conjugate();
// Here we rotate our panning vector about the the rotaion axis of any current rotation transform
// and then sum the new translation with any exisiting translation
qV = q * qV * qC;
m_Translate.OffsetX += qV.X;
m_Translate.OffsetY += qV.Y;
m_Translate.OffsetZ += qV.Z;
}
}
}
The GetXXXTransform3D methods are extension methods defined as follow:
public static ScaleTransform3D GetScaleTransform3D(this Transform3DGroup transform3DGroup)
{
ScaleTransform3D scaleTransform3D = null;
if (transform3DGroup != null)
{
foreach (var transform in transform3DGroup.Children)
{
scaleTransform3D = transform as ScaleTransform3D;
if (scaleTransform3D != null) return scaleTransform3D;
}
}
return scaleTransform3D;
}
public static RotateTransform3D GetRotateTransform3D(this Transform3DGroup transform3DGroup)
{
RotateTransform3D rotateTransform3D = null;
if (transform3DGroup != null)
{
foreach (var transform in transform3DGroup.Children)
{
rotateTransform3D = transform as RotateTransform3D;
if (rotateTransform3D != null) return rotateTransform3D;
}
}
return rotateTransform3D;
}
public static TranslateTransform3D GetTranslateTransform3D(this Transform3DGroup transform3DGroup)
{
TranslateTransform3D translateTransform3D = null;
if (transform3DGroup != null)
{
foreach (var transform in transform3DGroup.Children)
{
translateTransform3D = transform as TranslateTransform3D;
if (translateTransform3D != null) return translateTransform3D;
}
}
return translateTransform3D;
}
2nd, you need to declare a Transform to your PerspectiveCamera as follow:
(the example is taken from Sasha Barber's Elements3D project which I used to test this)
<Tools:TrackballDecorator x:Name="tbViewPort">
<Viewport3D x:Name="vpFeeds">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camera" Position="-2,2,40" LookDirection="2,-2,-40" FieldOfView="90">
<PerspectiveCamera.Transform>
<Transform3DGroup />
</PerspectiveCamera.Transform>
</PerspectiveCamera>
</Viewport3D.Camera>
<ContainerUIElement3D x:Name="container" />
<ModelVisual3D x:Name="model">
<ModelVisual3D.Content>
<DirectionalLight Color="White" Direction="-1,-1,-1" />
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Tools:TrackballDecorator>
3rd, since we are going to store each part of the whole transformation in a separate value, you need to create the relevant properties in your settings file, i.e. CameraScaleX, CameraScaleY, CameraScaleZ, CameraTranslateX, CameraTranslateY, CameraTranslateZ, CameraRotateAxisX, CameraRotateAxisY, CameraRotateAxisZ and CameraRotateAngle. All are of type double and are stored in User scope.
4th and last step is to actually save and load these settings into the camera using the following code:
private void SaveCameraSettings()
{
var transform3DGroup = camera.Transform as Transform3DGroup;
if (transform3DGroup != null)
{
foreach (var transform in transform3DGroup.Children)
{
var scale = transform as ScaleTransform3D;
if (scale != null) SaveCameraSetting(scale);
var rotate = transform as RotateTransform3D;
if (rotate != null) SaveCameraSetting(rotate);
var translate = transform as TranslateTransform3D;
if (translate != null) SaveCameraSetting(translate);
}
Settings.Default.Save();
}
}
private static void SaveCameraSetting(ScaleTransform3D transform)
{
Properties.Settings.Default.CameraScaleX = transform.ScaleX;
Properties.Settings.Default.CameraScaleY = transform.ScaleY;
Properties.Settings.Default.CameraScaleZ = transform.ScaleZ;
}
private static void SaveCameraSetting(RotateTransform3D transform)
{
var axisAngleRotation3D = transform.Rotation as AxisAngleRotation3D;
if (axisAngleRotation3D != null)
{
Properties.Settings.Default.CameraRotateAxisX = axisAngleRotation3D.Axis.X;
Properties.Settings.Default.CameraRotateAxisY = axisAngleRotation3D.Axis.Y;
Properties.Settings.Default.CameraRotateAxisZ = axisAngleRotation3D.Axis.Z;
Properties.Settings.Default.CameraRotateAngle = axisAngleRotation3D.Angle;
}
}
private static void SaveCameraSetting(TranslateTransform3D transform)
{
Properties.Settings.Default.CameraTranslateX = transform.OffsetX;
Properties.Settings.Default.CameraTranslateY = transform.OffsetY;
Properties.Settings.Default.CameraTranslateZ = transform.OffsetZ;
}
private void LoadCameraPosition()
{
var d = Settings.Default;
var transform3DGroup = new Transform3DGroup();
var scaleTransform3D = new ScaleTransform3D(d.CameraScaleX, d.CameraScaleY, d.CameraScaleZ);
var translateTransform3D = new TranslateTransform3D(d.CameraTranslateX, d.CameraTranslateY, d.CameraTranslateZ);
var axisAngleRotation3D = new AxisAngleRotation3D(new Vector3D(d.CameraRotateAxisX, d.CameraRotateAxisY, d.CameraRotateAxisZ),
d.CameraRotateAngle);
var rotateTransform3D = new RotateTransform3D(axisAngleRotation3D);
transform3DGroup.Children.Add(scaleTransform3D);
transform3DGroup.Children.Add(translateTransform3D);
transform3DGroup.Children.Add(rotateTransform3D);
tbViewPort.Transform = transform3DGroup;
}
Hopefully, I didn't forget anything. If you need more help or don't understand something, please don't hesitate to ask ;-)
You would need both the cameras view matrix data and the projection matrix data. The view matrix will contain the data about the position, rotation, scale and translation of the camera and the projection matrix will contain things like the field of view, near plane, far plane and other data.
Sorry I cant help with exporting/importing that data since I've not used WPF, but there might be rawdata properties exposed if it uses anything to do with as3's built in Matrix classes, this is usualy a as3 Vector. Object the the matrices 16 values exposed as row ordered floating point values.
I believe what you need is Position, LookDirection, UpDirection, FieldOfView, NearPlaneDistance, FarPlaneDistance. All the above properties define the camera.

Resources