diff --git a/Output/SharpVectors.Core.dll b/Output/SharpVectors.Core.dll index 3f7754ab4..6913e4781 100644 Binary files a/Output/SharpVectors.Core.dll and b/Output/SharpVectors.Core.dll differ diff --git a/Output/SharpVectors.Dom.dll b/Output/SharpVectors.Dom.dll index f8e5d2154..eee72c822 100644 Binary files a/Output/SharpVectors.Dom.dll and b/Output/SharpVectors.Dom.dll differ diff --git a/Output/SharpVectors.Runtime.Wpf.dll b/Output/SharpVectors.Runtime.Wpf.dll index 21b904896..e330df52b 100644 Binary files a/Output/SharpVectors.Runtime.Wpf.dll and b/Output/SharpVectors.Runtime.Wpf.dll differ diff --git a/Samples/WpfTestSvgControl/App.ico b/Samples/WpfTestSvgControl/App.ico new file mode 100644 index 000000000..bf398870c Binary files /dev/null and b/Samples/WpfTestSvgControl/App.ico differ diff --git a/Samples/WpfTestSvgControl/App.xaml b/Samples/WpfTestSvgControl/App.xaml new file mode 100644 index 000000000..ce9839f33 --- /dev/null +++ b/Samples/WpfTestSvgControl/App.xaml @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/App.xaml.cs b/Samples/WpfTestSvgControl/App.xaml.cs new file mode 100644 index 000000000..0dde326ea --- /dev/null +++ b/Samples/WpfTestSvgControl/App.xaml.cs @@ -0,0 +1,12 @@ +using System; +using System.Windows; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Samples/WpfTestSvgControl/DebugPage.xaml b/Samples/WpfTestSvgControl/DebugPage.xaml new file mode 100644 index 000000000..a6f8b8afa --- /dev/null +++ b/Samples/WpfTestSvgControl/DebugPage.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/Samples/WpfTestSvgControl/DebugPage.xaml.cs b/Samples/WpfTestSvgControl/DebugPage.xaml.cs new file mode 100644 index 000000000..70594d377 --- /dev/null +++ b/Samples/WpfTestSvgControl/DebugPage.xaml.cs @@ -0,0 +1,53 @@ +using System; + +using System.Windows.Controls; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for DebugPage.xaml + /// + public partial class DebugPage : Page + { + private MainWindow _mainWindow; + + public DebugPage() + { + InitializeComponent(); + } + + public MainWindow MainWindow + { + get { + return _mainWindow; + } + set { + _mainWindow = value; + } + } + + public void Startup() + { + if (traceDocument != null) + { + traceDocument.Startup(); + } + } + + public void Shutdown() + { + if (traceDocument != null) + { + traceDocument.Shutdown(); + } + } + + public void PageSelected(bool isSelected) + { + if (isSelected) + { + debugBox.Focus(); + } + } + } +} diff --git a/Samples/WpfTestSvgControl/DrawingHelpWindow.xaml b/Samples/WpfTestSvgControl/DrawingHelpWindow.xaml new file mode 100644 index 000000000..970e72644 --- /dev/null +++ b/Samples/WpfTestSvgControl/DrawingHelpWindow.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/DrawingHelpWindow.xaml.cs b/Samples/WpfTestSvgControl/DrawingHelpWindow.xaml.cs new file mode 100644 index 000000000..b14fe6100 --- /dev/null +++ b/Samples/WpfTestSvgControl/DrawingHelpWindow.xaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for DrawingHelpWindow.xaml + /// + public partial class DrawingHelpWindow : Window + { + public DrawingHelpWindow() + { + InitializeComponent(); + } + } +} diff --git a/Samples/WpfTestSvgControl/DrawingPage.xaml b/Samples/WpfTestSvgControl/DrawingPage.xaml new file mode 100644 index 000000000..083fead1c --- /dev/null +++ b/Samples/WpfTestSvgControl/DrawingPage.xaml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/DrawingPage.xaml.cs b/Samples/WpfTestSvgControl/DrawingPage.xaml.cs new file mode 100644 index 000000000..b46ca6027 --- /dev/null +++ b/Samples/WpfTestSvgControl/DrawingPage.xaml.cs @@ -0,0 +1,1403 @@ +using System; +using System.IO; +using System.Diagnostics; +using System.Globalization; +using System.Threading.Tasks; +using System.Collections.Generic; + +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Markup; +using System.Windows.Controls; +using System.Windows.Threading; + +using SharpVectors.Runtime; +using SharpVectors.Converters; +using SharpVectors.Renderers.Wpf; + +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Folding; +using ICSharpCode.AvalonEdit.Highlighting; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for DrawingPage.xaml + /// + public partial class DrawingPage : Page + { + #region Public Fields + + public const string TemporalDirName = "_Drawings"; + + #endregion + + #region Private Fields + + private const double ZoomChange = 0.1; + + private bool _isLoadingDrawing; + private bool _saveXaml; + + private string _drawingDir; + private string _svgFilePath; + private DirectoryInfo _directoryInfo; + + private FileSvgReader _fileReader; + private WpfDrawingSettings _wpfSettings; + + private DirectoryInfo _workingDir; + + /// + /// Specifies the current state of the mouse handling logic. + /// + private ZoomPanMouseHandlingMode _mouseHandlingMode; + + /// + /// The point that was clicked relative to the ZoomAndPanControl. + /// + private Point _origZoomAndPanControlMouseDownPoint; + + /// + /// The point that was clicked relative to the content that is contained within the ZoomAndPanControl. + /// + private Point _origContentMouseDownPoint; + + /// + /// Records which mouse button clicked during mouse dragging. + /// + private MouseButton _mouseButtonDown; + + /// + /// Saves the previous zoom rectangle, pressing the backspace key jumps back to this zoom rectangle. + /// + private Rect _prevZoomRect; + + /// + /// Save the previous content scale, pressing the backspace key jumps back to this scale. + /// + private double _prevZoomScale; + + /// + /// Set to 'true' when the previous zoom rect is saved. + /// + private bool _prevZoomRectSet; + + /// + /// Saves the next zoom rectangle, pressing the backspace key jumps back to this zoom rectangle. + /// + private Rect _nextZoomRect; + + /// + /// Save the next content scale, pressing the backspace key jumps back to this scale. + /// + private double _nextZoomScale; + + /// + /// Set to 'true' when the previous zoom rect is saved. + /// + private bool _nextZoomRectSet; + + private Cursor _panToolCursor; + private Cursor _panToolDownCursor; + + private Cursor _canvasCursor; + + private MainWindow _mainWindow; + private OptionSettings _optionSettings; + + private DispatcherTimer _dispatcherTimer; + + private string _selectedName; + + private WpfDrawingDocument _drawingDocument; + + private EmbeddedImageSerializerVisitor _embeddedImageVisitor; + private IList _embeddedImages; + + private FoldingManager _foldingManager; + private XmlFoldingStrategy _foldingStrategy; + + #endregion + + #region Constructors and Destructor + + public DrawingPage() + { + InitializeComponent(); + + _saveXaml = true; + _wpfSettings = new WpfDrawingSettings(); + _wpfSettings.CultureInfo = _wpfSettings.NeutralCultureInfo; + + _fileReader = new FileSvgReader(_wpfSettings); + _fileReader.SaveXaml = _saveXaml; + _fileReader.SaveZaml = false; + + _mouseHandlingMode = ZoomPanMouseHandlingMode.None; + + string workDir = Path.Combine(Path.GetDirectoryName( + System.Reflection.Assembly.GetExecutingAssembly().Location), TemporalDirName); + + _workingDir = new DirectoryInfo(workDir); + + _embeddedImages = new List(); + + _embeddedImageVisitor = new EmbeddedImageSerializerVisitor(true); + _wpfSettings.Visitors.ImageVisitor = _embeddedImageVisitor; + + _embeddedImageVisitor.ImageCreated += OnEmbeddedImageCreated; + + textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML"); + TextEditorOptions options = textEditor.Options; + if (options != null) + { + //options.AllowScrollBelowDocument = true; + options.EnableHyperlinks = true; + options.EnableEmailHyperlinks = true; + options.EnableVirtualSpace = false; + options.HighlightCurrentLine = true; + options.ShowSpaces = true; + options.ShowTabs = true; + options.ShowEndOfLine = true; + } + + textEditor.ShowLineNumbers = true; + textEditor.WordWrap = true; + + _foldingManager = FoldingManager.Install(textEditor.TextArea); + _foldingStrategy = new XmlFoldingStrategy(); + + this.Loaded += OnPageLoaded; + this.Unloaded += OnPageUnloaded; + this.SizeChanged += OnPageSizeChanged; + } + + private void OnEmbeddedImageCreated(object sender, EmbeddedImageSerializerArgs args) + { + if (args == null) + { + return; + } + if (_embeddedImages == null) + { + _embeddedImages = new List(); + } + _embeddedImages.Add(args); + } + + #endregion + + #region Public Properties + + public bool IsLoadingDrawing + { + get { + return _isLoadingDrawing; + } + } + + public string WorkingDrawingDir + { + get { + return _drawingDir; + } + set { + _drawingDir = value; + + if (!string.IsNullOrWhiteSpace(_drawingDir)) + { + _directoryInfo = new DirectoryInfo(_drawingDir); + + if (_fileReader != null) + { + _fileReader.SaveXaml = Directory.Exists(_drawingDir); + } + } + } + } + + public bool SaveXaml + { + get { + return _saveXaml; + } + set { + _saveXaml = value; + if (_fileReader != null) + { + _fileReader.SaveXaml = _saveXaml; + _fileReader.SaveZaml = false; + } + } + } + + public WpfDrawingSettings ConversionSettings + { + get { + return _wpfSettings; + } + set { + if (value != null) + { + _wpfSettings = value; + + // Recreated the conveter + _fileReader = new FileSvgReader(_wpfSettings); + _fileReader.SaveXaml = true; + _fileReader.SaveZaml = false; + + if (!string.IsNullOrWhiteSpace(_drawingDir) && + Directory.Exists(_drawingDir)) + { + _fileReader.SaveXaml = Directory.Exists(_drawingDir); + } + } + } + } + + public OptionSettings OptionSettings + { + get { + return _optionSettings; + } + set { + if (value != null) + { + _optionSettings = value; + this.ConversionSettings = value.ConversionSettings; + } + } + } + + public MainWindow MainWindow + { + get { + return _mainWindow; + } + set { + _mainWindow = value; + + if (_optionSettings == null) + { + this.OptionSettings = _mainWindow.OptionSettings; + } + } + } + + // + // Definitions for dependency properties. + // + /// + /// This allows the same property name to be used for direct and indirect access to the ZoomPanelControl control. + /// + public ZoomPanControl ZoomPanContent { + get { + return zoomPanControl; + } + } + + /// + /// This allows the same property name to be used for direct and indirect access to the SVG Canvas control. + /// + public SvgDrawingCanvas Viewer + { + get { + return svgViewer; + } + } + + public WpfDrawingDocument DrawingDocument + { + get { + return _drawingDocument; + } + } + + #endregion + + #region Public Methods + + public void SelectElement(string selectedName) + { + _selectedName = selectedName; + if (string.IsNullOrWhiteSpace(selectedName) || _drawingDocument == null) + { + elementImage.Source = null; + textEditor.Text = string.Empty; + + return; + } + + var selecteElement = _drawingDocument.GetSvgById(selectedName); + if (selecteElement != null) + { + textEditor.Text = selecteElement.OuterXml; + } + else + { + textEditor.Text = string.Empty; + } + + var selectedDrawing = _drawingDocument.GetById(selectedName); + if (selectedDrawing != null) + { + elementImage.Source = new DrawingImage(selectedDrawing); + } + else + { + elementImage.Source = null; + } + } + + public bool LoadDocument(string svgFilePath) + { + if (string.IsNullOrWhiteSpace(svgFilePath) || !File.Exists(svgFilePath)) + { + return false; + } + + DirectoryInfo workingDir = _workingDir; + if (_directoryInfo != null) + { + workingDir = _directoryInfo; + } + + this.UnloadDocument(true); + + _svgFilePath = svgFilePath; + _saveXaml = _optionSettings.ShowOutputFile; + + string fileExt = Path.GetExtension(svgFilePath); + + if (string.Equals(fileExt, SvgConverter.SvgExt, StringComparison.OrdinalIgnoreCase) || + string.Equals(fileExt, SvgConverter.CompressedSvgExt, StringComparison.OrdinalIgnoreCase)) + { + if (_fileReader != null) + { + _fileReader.SaveXaml = _saveXaml; + _fileReader.SaveZaml = false; + + _embeddedImageVisitor.SaveImages = !_wpfSettings.IncludeRuntime; + _embeddedImageVisitor.SaveDirectory = _drawingDir; + _wpfSettings.Visitors.ImageVisitor = _embeddedImageVisitor; + + DrawingGroup drawing = _fileReader.Read(svgFilePath, workingDir); + _drawingDocument = _fileReader.DrawingDocument; + if (drawing != null) + { + svgViewer.UnloadDiagrams(); + svgViewer.RenderDiagrams(drawing); + + Rect bounds = svgViewer.Bounds; + + if (bounds.IsEmpty) + { + bounds = new Rect(0, 0, zoomPanControl.ActualWidth, zoomPanControl.ActualHeight); + } + + zoomPanControl.AnimatedZoomTo(bounds); + CommandManager.InvalidateRequerySuggested(); + + return true; + } + } + } + else if (string.Equals(fileExt, SvgConverter.XamlExt, StringComparison.OrdinalIgnoreCase) || + string.Equals(fileExt, SvgConverter.CompressedXamlExt, StringComparison.OrdinalIgnoreCase)) + { + svgViewer.LoadDiagrams(svgFilePath); + + svgViewer.InvalidateMeasure(); + + return true; + } + + _svgFilePath = null; + + return false; + } + + public Task LoadDocumentAsync(string svgFilePath) + { + if (_isLoadingDrawing || string.IsNullOrWhiteSpace(svgFilePath) || !File.Exists(svgFilePath)) + { + return Task.FromResult(false); + } + + string fileExt = Path.GetExtension(svgFilePath); + + if (!(string.Equals(fileExt, SvgConverter.SvgExt, StringComparison.OrdinalIgnoreCase) || + string.Equals(fileExt, SvgConverter.CompressedSvgExt, StringComparison.OrdinalIgnoreCase))) + { + _svgFilePath = null; + return Task.FromResult(false); + } + + _isLoadingDrawing = true; + + this.UnloadDocument(true); + + DirectoryInfo workingDir = _workingDir; + if (_directoryInfo != null) + { + workingDir = _directoryInfo; + } + + _svgFilePath = svgFilePath; + _saveXaml = _optionSettings.ShowOutputFile; + + _embeddedImageVisitor.SaveImages = !_wpfSettings.IncludeRuntime; + _embeddedImageVisitor.SaveDirectory = _drawingDir; + _wpfSettings.Visitors.ImageVisitor = _embeddedImageVisitor; + + if (_fileReader == null) + { + _fileReader = new FileSvgReader(_wpfSettings); + _fileReader.SaveXaml = _saveXaml; + _fileReader.SaveZaml = false; + } + + var drawingStream = new MemoryStream(); + + // Get the UI thread's context + var context = TaskScheduler.FromCurrentSynchronizationContext(); + + return Task.Factory.StartNew(() => + { +// var saveXaml = _fileReader.SaveXaml; +// _fileReader.SaveXaml = true; // For threaded, we will save to avoid loading issue later... + DrawingGroup drawing = _fileReader.Read(svgFilePath, workingDir); +// _fileReader.SaveXaml = saveXaml; + _drawingDocument = _fileReader.DrawingDocument; + if (drawing != null) + { + XamlWriter.Save(drawing, drawingStream); + drawingStream.Seek(0, SeekOrigin.Begin); + + return true; + } + _svgFilePath = null; + return false; + }).ContinueWith((t) => { + try + { + if (!t.Result) + { + _isLoadingDrawing = false; + _svgFilePath = null; + return false; + } + if (drawingStream.Length != 0) + { + DrawingGroup drawing = (DrawingGroup)XamlReader.Load(drawingStream); + + svgViewer.UnloadDiagrams(); + svgViewer.RenderDiagrams(drawing); + + Rect bounds = svgViewer.Bounds; + + if (bounds.IsEmpty) + { + bounds = new Rect(0, 0, svgViewer.ActualWidth, svgViewer.ActualHeight); + } + + zoomPanControl.AnimatedZoomTo(bounds); + CommandManager.InvalidateRequerySuggested(); + + // The drawing changed, update the source... + _fileReader.Drawing = drawing; + } + + _isLoadingDrawing = false; + + return true; + } + catch + { + _isLoadingDrawing = false; + throw; + } + }, context); + } + + public void UnloadDocument(bool displayMessage = false) + { + try + { + elementImage.Source = null; + textEditor.Text = string.Empty; + + _svgFilePath = null; + _drawingDocument = null; + + if (svgViewer != null) + { + svgViewer.UnloadDiagrams(); + + if (displayMessage) + { + var drawing = this.DrawText("Loading..."); + + svgViewer.RenderDiagrams(drawing); + + Rect bounds = svgViewer.Bounds; + if (bounds.IsEmpty) + { + bounds = drawing.Bounds; + } + + zoomPanControl.ZoomTo(bounds); + return; + } + } + + var drawRect = this.DrawRect(); + svgViewer.RenderDiagrams(drawRect); + + zoomPanControl.ZoomTo(drawRect.Bounds); + ClearPrevZoomRect(); + ClearNextZoomRect(); + } + finally + { + if (_embeddedImages != null && _embeddedImages.Count != 0) + { + foreach (var embeddedImage in _embeddedImages) + { + try + { + if (embeddedImage.Image != null) + { + if (embeddedImage.Image.StreamSource != null) + { + embeddedImage.Image.StreamSource.Dispose(); + } + } + + var imagePath = embeddedImage.ImagePath; + if (!string.IsNullOrWhiteSpace(imagePath) && File.Exists(imagePath)) + { + File.Delete(imagePath); + } + } + catch (IOException ex) + { + Trace.TraceError(ex.ToString()); + // Image this, WPF will typically cache and/or lock loaded images + } + } + + _embeddedImages.Clear(); + } + } + } + + public bool SaveDocument(string fileName) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + return false; + } + + if (_fileReader == null || _fileReader.Drawing == null) + { + return false; + } + return _fileReader.Save(fileName, true, false); + } + + public void PageSelected(bool isSelected) + { + if (isSelected) + { + svgViewer.Focus(); + + if (zoomPanControl.IsKeyboardFocusWithin) + { + Keyboard.Focus(zoomPanControl); + } + } + } + + public void SaveZoom() + { + if (zoomPanControl != null) + { + SavePrevZoomRect(); + + ClearNextZoomRect(); + } + } + + #endregion + + #region Protected Methods + + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + } + + #endregion + + #region Private Event Handlers (Page) + + private void OnPageLoaded(object sender, RoutedEventArgs e) + { + if (string.IsNullOrWhiteSpace(_svgFilePath) || !File.Exists(_svgFilePath)) + { + zoomPanControl.ContentScale = 1.0; + + if (zoomPanControl != null) + { + zoomPanControl.IsMouseWheelScrollingEnabled = true; + } + + if (string.IsNullOrWhiteSpace(_svgFilePath)) + { + this.UnloadDocument(); + } + } + + try + { + if (_panToolCursor == null) + { + var panToolStream = Application.GetResourceStream(new Uri("Resources/PanTool.cur", UriKind.Relative)); + using (panToolStream.Stream) + { + _panToolCursor = new Cursor(panToolStream.Stream); + } + } + if (_panToolDownCursor == null) + { + var panToolDownStream = Application.GetResourceStream(new Uri("Resources/PanToolDown.cur", UriKind.Relative)); + using (panToolDownStream.Stream) + { + _panToolDownCursor = new Cursor(panToolDownStream.Stream); + } + } + + // DispatcherTimer setup + if (_dispatcherTimer == null) + { + _dispatcherTimer = new DispatcherTimer(); + _dispatcherTimer.Tick += OnUpdateUITick; + _dispatcherTimer.Interval = new TimeSpan(0, 0, 1); + } + } + catch (Exception ex) + { + Trace.TraceError(ex.ToString()); + } + + if (zoomPanControl != null && zoomPanControl.ScrollOwner == null) + { + if (canvasScroller != null) + { + zoomPanControl.ScrollOwner = canvasScroller; + } + } + + if (_dispatcherTimer != null) + { + _dispatcherTimer.Start(); + } + } + + private void OnPageUnloaded(object sender, RoutedEventArgs e) + { + if (_dispatcherTimer != null) + { + _dispatcherTimer.Stop(); + } + } + + private void OnPageSizeChanged(object sender, SizeChangedEventArgs e) + { + if (zoomPanControl != null && svgViewer != null) + { + svgViewer.InvalidateMeasure(); + svgViewer.UpdateLayout(); + + Rect bounds = svgViewer.Bounds; + + if (bounds.IsEmpty) + { + bounds = new Rect(0, 0, svgViewer.ActualWidth, svgViewer.ActualHeight); + } + + //zoomPanControl.AnimatedZoomTo(bounds); + zoomPanControl.AnimatedZoomTo(this.FitZoomValue); + CommandManager.InvalidateRequerySuggested(); + } + } + + private async void OnOpenFileClick(object sender, RoutedEventArgs e) + { + if (_mainWindow != null) + { + await _mainWindow.BrowseForFile(); + } + } + + private void OnOpenFolderClick(object sender, RoutedEventArgs e) + { + } + + private void OnShowHelp(object sender, RoutedEventArgs e) + { + var helpDialog = new DrawingHelpWindow(); + + if (_mainWindow != null) + { + helpDialog.Left = _mainWindow.Left + _mainWindow.ActualWidth - helpDialog.Width; + helpDialog.Top = _mainWindow.Top + _mainWindow.ActualHeight - helpDialog.Height; + helpDialog.Owner = _mainWindow; + helpDialog.WindowStartupLocation = WindowStartupLocation.Manual; + } + + helpDialog.Show(); + } + + /// + /// Updates the current seconds display and calls InvalidateRequerySuggested on the + /// CommandManager to force the Command to raise the CanExecuteChanged event. + /// + /// + /// + private void OnUpdateUITick(object sender, EventArgs e) + { + // Forcing the CommandManager to raise the RequerySuggested event + CommandManager.InvalidateRequerySuggested(); + } + + #endregion + + #region Private Zoom Panel Handlers + + /// + /// Event raised on mouse down in the ZoomAndPanControl. + /// + private void OnZoomPanMouseDown(object sender, MouseButtonEventArgs e) + { + zoomPanControl.Focus(); + Keyboard.Focus(zoomPanControl); + + _mouseButtonDown = e.ChangedButton; + _origZoomAndPanControlMouseDownPoint = e.GetPosition(zoomPanControl); + _origContentMouseDownPoint = e.GetPosition(svgViewer); + + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.SelectPoint || + _mouseHandlingMode == ZoomPanMouseHandlingMode.SelectRectangle) + { + } + else + { + if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0 && + (e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Right)) + { + // Shift + left- or right-down initiates zooming mode. + _mouseHandlingMode = ZoomPanMouseHandlingMode.Zooming; + + if (zoomPanControl != null && _canvasCursor != null) + { + zoomPanControl.Cursor = _canvasCursor; + } + } + else if (_mouseButtonDown == MouseButton.Left) + { + // Just a plain old left-down initiates panning mode. + _mouseHandlingMode = ZoomPanMouseHandlingMode.Panning; + } + + if (_mouseHandlingMode != ZoomPanMouseHandlingMode.None) + { + // Capture the mouse so that we eventually receive the mouse up event. + zoomPanControl.CaptureMouse(); + e.Handled = true; + } + } + + } + + /// + /// Event raised on mouse up in the ZoomAndPanControl. + /// + private void OnZoomPanMouseUp(object sender, MouseButtonEventArgs e) + { + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.SelectPoint || + _mouseHandlingMode == ZoomPanMouseHandlingMode.SelectRectangle) + { + } + else + { + if (_mouseHandlingMode != ZoomPanMouseHandlingMode.None) + { + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Zooming) + { + if (_mouseButtonDown == MouseButton.Left) + { + // Shift + left-click zooms in on the content. + ZoomIn(_origContentMouseDownPoint); + } + else if (_mouseButtonDown == MouseButton.Right) + { + // Shift + left-click zooms out from the content. + ZoomOut(_origContentMouseDownPoint); + } + } + else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.DragZooming) + { + // When drag-zooming has finished we zoom in on the rectangle that was highlighted by the user. + ApplyDragZoomRect(); + } + + zoomPanControl.ReleaseMouseCapture(); + _mouseHandlingMode = ZoomPanMouseHandlingMode.None; + e.Handled = true; + } + + if (zoomPanControl != null && _canvasCursor != null) + { + zoomPanControl.Cursor = _canvasCursor; + } + } + } + + /// + /// Event raised on mouse move in the ZoomAndPanControl. + /// + private void OnZoomPanMouseMove(object sender, MouseEventArgs e) + { + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.SelectPoint || + _mouseHandlingMode == ZoomPanMouseHandlingMode.SelectRectangle) + { + } + else + { + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Panning) + { + if (zoomPanControl != null) + { + zoomPanControl.Cursor = _panToolCursor; + } + + // + // The user is left-dragging the mouse. + // Pan the viewport by the appropriate amount. + // + Point curContentMousePoint = e.GetPosition(svgViewer); + Vector dragOffset = curContentMousePoint - _origContentMouseDownPoint; + + zoomPanControl.ContentOffsetX -= dragOffset.X; + zoomPanControl.ContentOffsetY -= dragOffset.Y; + + e.Handled = true; + } + else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Zooming) + { + if (zoomPanControl != null && _canvasCursor != null) + { + zoomPanControl.Cursor = _canvasCursor; + } + + Point curZoomAndPanControlMousePoint = e.GetPosition(zoomPanControl); + Vector dragOffset = curZoomAndPanControlMousePoint - _origZoomAndPanControlMouseDownPoint; + double dragThreshold = 10; + if (_mouseButtonDown == MouseButton.Left && + (Math.Abs(dragOffset.X) > dragThreshold || + Math.Abs(dragOffset.Y) > dragThreshold)) + { + // + // When Shift + left-down zooming mode and the user drags beyond the drag threshold, + // initiate drag zooming mode where the user can drag out a rectangle to select the area + // to zoom in on. + // + _mouseHandlingMode = ZoomPanMouseHandlingMode.DragZooming; + + Point curContentMousePoint = e.GetPosition(svgViewer); + InitDragZoomRect(_origContentMouseDownPoint, curContentMousePoint); + } + + e.Handled = true; + } + else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.DragZooming) + { + if (zoomPanControl != null && _canvasCursor != null) + { + zoomPanControl.Cursor = _canvasCursor; + } + + // + // When in drag zooming mode continously update the position of the rectangle + // that the user is dragging out. + // + Point curContentMousePoint = e.GetPosition(svgViewer); + SetDragZoomRect(_origContentMouseDownPoint, curContentMousePoint); + + e.Handled = true; + } + } + } + + /// + /// Event raised by rotating the mouse wheel + /// + private void OnZoomPanMouseWheel(object sender, MouseWheelEventArgs e) + { + e.Handled = true; + + Point curContentMousePoint = e.GetPosition(svgViewer); + //if (e.Delta > 0) + //{ + // ZoomIn(curContentMousePoint); + //} + //else if (e.Delta < 0) + //{ + // ZoomOut(curContentMousePoint); + //} + this.Zoom(curContentMousePoint, e.Delta); + + if (svgViewer.IsKeyboardFocusWithin) + { + Keyboard.Focus(zoomPanControl); + } + } + + /// + /// Event raised by double-left click + /// + private void OnZoomPanMouseDoubleClick(object sender, MouseButtonEventArgs e) + { + if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0) + { + SavePrevZoomRect(); + + zoomPanControl.AnimatedSnapTo(e.GetPosition(svgViewer)); + + ClearNextZoomRect(); + + e.Handled = true; + } + } + + /// + /// The 'Pan' command (bound to the plus key) was executed. + /// + private void OnPanMode(object sender, RoutedEventArgs e) + { + } + + /// + /// Determines whether the 'Pan' command can be executed. + /// + private void OnCanPanMode(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = false; + } + + /// + /// The 'ZoomReset' command (bound to the plus key) was executed. + /// + private void OnZoomReset(object sender, RoutedEventArgs e) + { + SavePrevZoomRect(); + + zoomPanControl.AnimatedZoomTo(1.0); + + ClearNextZoomRect(); + } + + /// + /// Determines whether the 'ZoomReset' command can be executed. + /// + private void OnCanZoomReset(object sender, CanExecuteRoutedEventArgs e) + { + if (zoomPanControl == null) + { + e.CanExecute = false; + return; + } + e.CanExecute = !zoomPanControl.ContentScale.Equals(1.0); + } + + /// + /// The 'ZoomFit/Fill' command (bound to the plus key) was executed. + /// + private void OnZoomFit(object sender, RoutedEventArgs e) + { + SavePrevZoomRect(); + + //zoomPanControl.AnimatedScaleToFit(); + zoomPanControl.AnimatedZoomTo(this.FitZoomValue); + + ClearNextZoomRect(); + } + + /// + /// Determines whether the 'ZoomFit' command can be executed. + /// + private void OnCanZoomFit(object sender, CanExecuteRoutedEventArgs e) + { + if (zoomPanControl == null) + { + e.CanExecute = false; + return; + } + + var fitValue = this.FitZoomValue; + + e.CanExecute = !IsWithinOnePercent(zoomPanControl.ContentScale, fitValue) + && fitValue >= zoomPanControl.MinContentScale; + } + + /// + /// The 'ZoomIn' command (bound to the plus key) was executed. + /// + private void OnZoomIn(object sender, RoutedEventArgs e) + { + SavePrevZoomRect(); + + ZoomIn(new Point(zoomPanControl.ContentZoomFocusX, zoomPanControl.ContentZoomFocusY)); + + ClearNextZoomRect(); + } + + /// + /// Determines whether the 'ZoomIn' command can be executed. + /// + private void OnCanZoomIn(object sender, CanExecuteRoutedEventArgs e) + { + if (zoomPanControl == null) + { + e.CanExecute = false; + return; + } + e.CanExecute = zoomPanControl.ContentScale < zoomPanControl.MaxContentScale; + } + + /// + /// The 'ZoomOut' command (bound to the minus key) was executed. + /// + private void OnZoomOut(object sender, RoutedEventArgs e) + { + SavePrevZoomRect(); + + ZoomOut(new Point(zoomPanControl.ContentZoomFocusX, zoomPanControl.ContentZoomFocusY)); + + ClearNextZoomRect(); + } + + /// + /// Determines whether the 'UndoZoom' command can be executed. + /// + private void OnCanZoomOut(object sender, CanExecuteRoutedEventArgs e) + { + if (zoomPanControl == null) + { + e.CanExecute = false; + return; + } + e.CanExecute = zoomPanControl.ContentScale > zoomPanControl.MinContentScale; + } + + /// + /// The 'UndoZoom' command was executed. + /// + private void OnUndoZoom(object sender, ExecutedRoutedEventArgs e) + { + UndoZoom(); + } + + /// + /// Determines whether the 'UndoZoom' command can be executed. + /// + private void OnCanUndoZoom(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = _prevZoomRectSet; + } + + /// + /// The 'RedoZoom' command was executed. + /// + private void OnRedoZoom(object sender, ExecutedRoutedEventArgs e) + { + RedoZoom(); + } + + /// + /// Determines whether the 'RedoZoom' command can be executed. + /// + private void OnCanRedoZoom(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = _nextZoomRectSet; + } + + /// + /// Jump back to the previous zoom level. + /// + private void UndoZoom() + { + SaveNextZoomRect(); + + zoomPanControl.AnimatedZoomTo(_prevZoomScale, _prevZoomRect); + + ClearPrevZoomRect(); + } + + /// + /// Jump back to the next zoom level. + /// + private void RedoZoom() + { + SavePrevZoomRect(); + + zoomPanControl.AnimatedZoomTo(_nextZoomScale, _nextZoomRect); + + ClearNextZoomRect(); + } + + private void Zoom(Point contentZoomCenter, int wheelMouseDelta) + { + SavePrevZoomRect(); + + // Found the division by 3 gives a little smoothing effect + var zoomFactor = zoomPanControl.ContentScale + ZoomChange * wheelMouseDelta / (120 * 3); + + zoomPanControl.ZoomAboutPoint(zoomFactor, contentZoomCenter); + + ClearNextZoomRect(); + } + + /// + /// Zoom the viewport out, centering on the specified point (in content coordinates). + /// + private void ZoomOut(Point contentZoomCenter) + { + SavePrevZoomRect(); + + zoomPanControl.ZoomAboutPoint(zoomPanControl.ContentScale - ZoomChange, contentZoomCenter); + + ClearNextZoomRect(); + } + + /// + /// Zoom the viewport in, centering on the specified point (in content coordinates). + /// + private void ZoomIn(Point contentZoomCenter) + { + SavePrevZoomRect(); + + zoomPanControl.ZoomAboutPoint(zoomPanControl.ContentScale + ZoomChange, contentZoomCenter); + + ClearNextZoomRect(); + } + + /// + /// Initialise the rectangle that the use is dragging out. + /// + private void InitDragZoomRect(Point pt1, Point pt2) + { + SetDragZoomRect(pt1, pt2); + + dragZoomCanvas.Visibility = Visibility.Visible; + dragZoomBorder.Opacity = 0.5; + } + + /// + /// Update the position and size of the rectangle that user is dragging out. + /// + private void SetDragZoomRect(Point pt1, Point pt2) + { + double x, y, width, height; + + // + // Deterine x,y,width and height of the rect inverting the points if necessary. + // + + if (pt2.X < pt1.X) + { + x = pt2.X; + width = pt1.X - pt2.X; + } + else + { + x = pt1.X; + width = pt2.X - pt1.X; + } + + if (pt2.Y < pt1.Y) + { + y = pt2.Y; + height = pt1.Y - pt2.Y; + } + else + { + y = pt1.Y; + height = pt2.Y - pt1.Y; + } + + // + // Update the coordinates of the rectangle that is being dragged out by the user. + // The we offset and rescale to convert from content coordinates. + // + Canvas.SetLeft(dragZoomBorder, x); + Canvas.SetTop(dragZoomBorder, y); + dragZoomBorder.Width = width; + dragZoomBorder.Height = height; + } + + /// + /// When the user has finished dragging out the rectangle the zoom operation is applied. + /// + private void ApplyDragZoomRect() + { + // + // Record the previous zoom level, so that we can jump back to it when the backspace key is pressed. + // + SavePrevZoomRect(); + + // + // Retreive the rectangle that the user draggged out and zoom in on it. + // + double contentX = Canvas.GetLeft(dragZoomBorder); + double contentY = Canvas.GetTop(dragZoomBorder); + double contentWidth = dragZoomBorder.Width; + double contentHeight = dragZoomBorder.Height; + zoomPanControl.AnimatedZoomTo(new Rect(contentX, contentY, contentWidth, contentHeight)); + + FadeOutDragZoomRect(); + + ClearNextZoomRect(); + } + + // + // Fade out the drag zoom rectangle. + // + private void FadeOutDragZoomRect() + { + ZoomPanAnimationHelper.StartAnimation(dragZoomBorder, OpacityProperty, 0.0, ZoomChange, + delegate (object sender, EventArgs e) + { + dragZoomCanvas.Visibility = Visibility.Collapsed; + }); + } + + // + // Record the previous zoom level, so that we can jump back to it when the backspace key is pressed. + // + private void SavePrevZoomRect() + { + _prevZoomRect = new Rect(zoomPanControl.ContentOffsetX, zoomPanControl.ContentOffsetY, + zoomPanControl.ContentViewportWidth, zoomPanControl.ContentViewportHeight); + _prevZoomScale = zoomPanControl.ContentScale; + _prevZoomRectSet = true; + } + + // + // Record the next zoom level, so that we can jump back to it when the backspace key is pressed. + // + private void SaveNextZoomRect() + { + _nextZoomRect = new Rect(zoomPanControl.ContentOffsetX, zoomPanControl.ContentOffsetY, + zoomPanControl.ContentViewportWidth, zoomPanControl.ContentViewportHeight); + _nextZoomScale = zoomPanControl.ContentScale; + _nextZoomRectSet = true; + } + + /// + /// Clear the memory of the previous zoom level. + /// + private void ClearPrevZoomRect() + { + _prevZoomRectSet = false; + } + + /// + /// Clear the memory of the next zoom level. + /// + private void ClearNextZoomRect() + { + _nextZoomRectSet = false; + } + + public double FitZoomValue + { + get { + if (zoomPanControl == null) + { + return 1; + } + + var content = zoomPanControl.ContentElement; + + return FitZoom(ActualWidth, ActualHeight, content?.ActualWidth, content?.ActualHeight); + } + } + + private static bool IsWithinOnePercent(double value, double testValue) + { + return Math.Abs(value - testValue) < .01 * testValue; + } + + private static double FitZoom(double actualWidth, double actualHeight, double? contentWidth, double? contentHeight) + { + if (!contentWidth.HasValue || !contentHeight.HasValue) return 1; + return Math.Min(actualWidth / contentWidth.Value, actualHeight / contentHeight.Value); + } + + #endregion + + #region Private Methods + + private DrawingGroup DrawRect() + { + // Create a new DrawingGroup of the control. + DrawingGroup drawingGroup = new DrawingGroup(); + + // Open the DrawingGroup in order to access the DrawingContext. + using (DrawingContext drawingContext = drawingGroup.Open()) + { + drawingContext.DrawRectangle(Brushes.White, null, new Rect(0, 0, 280, 300)); + } + // Return the updated DrawingGroup content to be used by the control. + return drawingGroup; + } + + // Convert the text string to a geometry and draw it to the control's DrawingContext. + private DrawingGroup DrawText(string textString) + { + // Create a new DrawingGroup of the control. + DrawingGroup drawingGroup = new DrawingGroup(); + + drawingGroup.Opacity = 0.8; + + // Open the DrawingGroup in order to access the DrawingContext. + using (DrawingContext drawingContext = drawingGroup.Open()) + { + // Create the formatted text based on the properties set. + var formattedText = new FormattedText(textString, + CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, + new Typeface(new FontFamily("Tahoma"), FontStyles.Normal, + FontWeights.Normal, FontStretches.Normal), 72, Brushes.Black); + + // Build the geometry object that represents the text. + Geometry textGeometry = formattedText.BuildGeometry(new Point(20, 0)); + + drawingContext.DrawRoundedRectangle(Brushes.Transparent, null, + new Rect(new Size(formattedText.Width + 50, formattedText.Height + 5)), 5.0, 5.0); + + // Draw the outline based on the properties that are set. + drawingContext.DrawGeometry(null, new Pen(Brushes.DarkGray, 1.5), textGeometry); + } + + // Return the updated DrawingGroup content to be used by the control. + return drawingGroup; + } + + #endregion + } +} diff --git a/Samples/WpfTestSvgControl/DrawingPageHelp.xaml b/Samples/WpfTestSvgControl/DrawingPageHelp.xaml new file mode 100644 index 000000000..9c5ce1a07 --- /dev/null +++ b/Samples/WpfTestSvgControl/DrawingPageHelp.xaml @@ -0,0 +1,78 @@ + + + + + + + Mouse and Keyboard Controls + + + + Control + Plus key = zoom in + + + Control + Minus key = zoom out + + + Left-drag = panning + + + Left-drag = drag the rectangles + + + Shift + left-drag = drag out a rectangle to zoom to + + + Control + Z = jump back to previous zoom level (Undo) + + + Control + Y = jump back to next zoom level (Redo) + + + Shift + left-click = zoom in + + + Shift + right-click = zoom out + + + Double-left-click = center on the clicked location + + + Mouse wheel forward = zoom in + + + Mouse wheel backward = zoom out + + + + Overview Window + + + + Left-drag = drag the overview mode rectangle about (can also drag the rectangles). + + + Double-left-click = snap the overview mode rectangle to a particular point. + + + + + + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/GridExpander.cs b/Samples/WpfTestSvgControl/GridExpander.cs new file mode 100644 index 000000000..cd475dea0 --- /dev/null +++ b/Samples/WpfTestSvgControl/GridExpander.cs @@ -0,0 +1,625 @@ +// +// Based on codes from +// https://jefuri.wordpress.com/2010/09/15/gridexpander-for-wpf/ +// + +using System; +using System.Windows; +using System.Windows.Shapes; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Media.Animation; + +namespace WpfTestSvgControl +{ + /// + /// Specifies different collapse modes of a GridExpander. + /// + public enum GridExpanderDirection + { + /// + /// The GridExpander cannot be collapsed or expanded. + /// + None = 0, + /// + /// The column (or row) to the right (or below) the + /// splitter's column, will be collapsed. + /// + Next = 1, + /// + /// The column (or row) to the left (or above) the + /// splitter's column, will be collapsed. + /// + Previous = 2 + } + + /// + /// An updated version of the standard GridExpander control that includes a centered handle + /// which allows complete collapsing and expanding of the appropriate grid column or row. + /// + [TemplatePart(Name = ElementHandleName, Type = typeof(ToggleButton))] + [TemplatePart(Name = ElementTemplateName, Type = typeof(FrameworkElement))] + [TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")] + [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")] + [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")] + [TemplateVisualState(Name = "Focused", GroupName = "FocusStates")] + [TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")] + public class GridExpander : GridSplitter + { + #region Private Fields + + /// + /// An enumeration that specifies the direction the GridExpander will + /// be collapased (Rows or Columns). + /// + private enum GridCollapseOrientation + { + Auto, + Columns, + Rows + } + + private const string ElementHandleName = "ExpanderHandle"; + private const string ElementTemplateName = "TheTemplate"; + private const string ElementGridExpanderBackground = "GridExpanderBackground"; + + private ToggleButton _expanderButton; + private Rectangle _elementGridExpanderBackground; + + private RowDefinition AnimatingRow; + private ColumnDefinition AnimatingColumn; + + private GridCollapseOrientation _gridCollapseDirection = GridCollapseOrientation.Auto; + private GridLength _savedGridLength; + private double _savedActualValue; + private double _animationTimeMillis = 200; + + #endregion + + #region Dependency properties + + /// + /// Identifies the Direction dependency property + /// + public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register( + "Direction", typeof(GridExpanderDirection), typeof(GridExpander), + new PropertyMetadata(GridExpanderDirection.Next, new PropertyChangedCallback(OnDirectionPropertyChanged))); + + /// + /// Identifies the HandleStyle dependency property + /// + public static readonly DependencyProperty HandleStyleProperty = DependencyProperty.Register( + "HandleStyle", typeof(Style), typeof(GridExpander), null); + + /// + /// Identifies the IsAnimated dependency property + /// + public static readonly DependencyProperty IsAnimatedProperty = DependencyProperty.Register( + "IsAnimated", typeof(bool), typeof(GridExpander), null); + + /// + /// Identifies the IsCollapsed dependency property + /// + public static readonly DependencyProperty IsCollapsedProperty = DependencyProperty.Register( + "IsCollapsed", typeof(bool), typeof(GridExpander), + new PropertyMetadata(new PropertyChangedCallback(OnIsCollapsedPropertyChanged))); + + private static readonly DependencyProperty RowHeightAnimationProperty = DependencyProperty.Register( + "RowHeightAnimation", typeof(double), typeof(GridExpander), + new PropertyMetadata(new PropertyChangedCallback(RowHeightAnimationChanged))); + + private static readonly DependencyProperty ColWidthAnimationProperty = DependencyProperty.Register( + "ColWidthAnimation", typeof(double), typeof(GridExpander), + new PropertyMetadata(new PropertyChangedCallback(ColWidthAnimationChanged))); + + #endregion + + #region Public Events + + // Define Collapsed and Expanded evenets + public event EventHandler Collapsed; + public event EventHandler Expanded; + + #endregion + + #region Constructors and Destructor + + /// + /// Initializes a new instance of the GridExpander class, + /// which inherits from System.Windows.Controls.GridExpander. + /// + public GridExpander() + { + // Set default values + //DefaultStyleKey = typeof(GridExpander); + + VisualStateManager.GoToState(this, "Checked", false); + + //Direction = GridExpanderDirection.None; + this.IsAnimated = true; + this.LayoutUpdated += delegate + { + _gridCollapseDirection = GetCollapseDirection(); + }; + + // All GridExpander visual states are handled by the parent GridSplitter class. + + this.Direction = GridExpanderDirection.Next; + } + + static GridExpander() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(GridExpander), + new FrameworkPropertyMetadata(typeof(GridExpander))); + } + + #endregion + + #region Public and Private Properties + + /// + /// Gets or sets a value that indicates the direction in which the row/colum + /// will be located that is to be expanded and collapsed. + /// + public GridExpanderDirection Direction + { + get { return (GridExpanderDirection)GetValue(DirectionProperty); } + set { SetValue(DirectionProperty, value); } + } + + /// + /// Gets or sets the style that customizes the appearance of the vertical handle + /// that is used to expand and collapse the GridExpander. + /// + public Style HandleStyle + { + get { return (Style)GetValue(HandleStyleProperty); } + set { SetValue(HandleStyleProperty, value); } + } + + /// + /// Gets or sets a value that indicates if the collapse and + /// expanding actions should be animated. + /// + public bool IsAnimated + { + get { return (bool)GetValue(IsAnimatedProperty); } + set { SetValue(IsAnimatedProperty, value); } + } + + /// + /// Gets or sets a value that indicates if the target column is + /// currently collapsed. + /// + public bool IsCollapsed + { + get { return (bool)GetValue(IsCollapsedProperty); } + set { SetValue(IsCollapsedProperty, value); } + } + + private double RowHeightAnimation + { + get { return (double)GetValue(RowHeightAnimationProperty); } + set { SetValue(RowHeightAnimationProperty, value); } + } + + private double ColWidthAnimation + { + get { return (double)GetValue(ColWidthAnimationProperty); } + set { SetValue(ColWidthAnimationProperty, value); } + } + + #endregion + + #region Public Methods + + /// + /// This method is called when the tempalte should be applied to the control. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _expanderButton = GetTemplateChild(ElementHandleName) as ToggleButton; + _elementGridExpanderBackground = GetTemplateChild(ElementGridExpanderBackground) as Rectangle; + + // Wire up the Checked and Unchecked events of the VerticalGridExpanderHandle. + if (_expanderButton != null) + { + _expanderButton.Checked += GridExpanderButton_Checked; + _expanderButton.Unchecked += OnExpanderButtonUnchecked; + } + + // Set default direction since we don't have all the components layed out yet. + _gridCollapseDirection = GridCollapseOrientation.Auto; + + // Directely call these events so design-time view updates appropriately + OnDirectionChanged(Direction); + OnIsCollapsedChanged(IsCollapsed); + } + + #endregion + + #region Protected Methods + + /// + /// Handles the property change event of the IsCollapsed property. + /// + /// The new value for the IsCollapsed property. + protected virtual void OnIsCollapsedChanged(bool isCollapsed) + { + _expanderButton.IsChecked = isCollapsed; + } + + /// + /// Handles the property change event of the Direction property. + /// + /// The new value for the Direction property. + protected virtual void OnDirectionChanged(GridExpanderDirection direction) + { + if (_expanderButton == null) + { + // There is no expander button so don't attempt to modify it + return; + } + + // TODO: Use triggers for setting visibility conditionally instead of doing it here + if (direction == GridExpanderDirection.None) + { + // Hide the handles if the Direction is set to None. + _expanderButton.Visibility = Visibility.Collapsed; + } + else + { + // Ensure the handle is Visible. + _expanderButton.Visibility = Visibility.Visible; + } + } + + /// + /// Raises the Collapsed event. + /// + /// Contains event arguments. + protected virtual void OnCollapsed(EventArgs e) + { + this.Collapsed?.Invoke(this, e); + } + + /// + /// Raises the Expanded event. + /// + /// Contains event arguments. + protected virtual void OnExpanded(EventArgs e) + { + this.Expanded?.Invoke(this, e); + } + + #endregion + + #region Private Methods + + /// + /// Collapses the target ColumnDefinition or RowDefinition. + /// + private void Collapse() + { + Grid parentGrid = base.Parent as Grid; + int splitterIndex = int.MinValue; + + if (_gridCollapseDirection == GridCollapseOrientation.Rows) + { + // Get the index of the row containing the splitter + splitterIndex = (int)base.GetValue(Grid.RowProperty); + + // Determing the curent Direction + if (this.Direction == GridExpanderDirection.Next) + { + // Save the next rows Height information + _savedGridLength = parentGrid.RowDefinitions[splitterIndex + 1].Height; + _savedActualValue = parentGrid.RowDefinitions[splitterIndex + 1].ActualHeight; + + // Collapse the next row + if (IsAnimated) + AnimateCollapse(parentGrid.RowDefinitions[splitterIndex + 1]); + else + parentGrid.RowDefinitions[splitterIndex + 1].SetValue(RowDefinition.HeightProperty, new GridLength(0)); + } + else + { + // Save the previous row's Height information + _savedGridLength = parentGrid.RowDefinitions[splitterIndex - 1].Height; + _savedActualValue = parentGrid.RowDefinitions[splitterIndex - 1].ActualHeight; + + // Collapse the previous row + if (IsAnimated) + AnimateCollapse(parentGrid.RowDefinitions[splitterIndex - 1]); + else + parentGrid.RowDefinitions[splitterIndex - 1].SetValue(RowDefinition.HeightProperty, new GridLength(0)); + } + } + else + { + // Get the index of the column containing the splitter + splitterIndex = (int)base.GetValue(Grid.ColumnProperty); + + // Determing the curent Direction + if (this.Direction == GridExpanderDirection.Next) + { + // Save the next column's Width information + _savedGridLength = parentGrid.ColumnDefinitions[splitterIndex + 1].Width; + _savedActualValue = parentGrid.ColumnDefinitions[splitterIndex + 1].ActualWidth; + + // Collapse the next column + if (IsAnimated) + AnimateCollapse(parentGrid.ColumnDefinitions[splitterIndex + 1]); + else + parentGrid.ColumnDefinitions[splitterIndex + 1].SetValue(ColumnDefinition.WidthProperty, new GridLength(0)); + } + else + { + // Save the previous column's Width information + _savedGridLength = parentGrid.ColumnDefinitions[splitterIndex - 1].Width; + _savedActualValue = parentGrid.ColumnDefinitions[splitterIndex - 1].ActualWidth; + + // Collapse the previous column + if (IsAnimated) + AnimateCollapse(parentGrid.ColumnDefinitions[splitterIndex - 1]); + else + parentGrid.ColumnDefinitions[splitterIndex - 1].SetValue(ColumnDefinition.WidthProperty, new GridLength(0)); + } + } + + } + + /// + /// Expands the target ColumnDefinition or RowDefinition. + /// + private void Expand() + { + Grid parentGrid = base.Parent as Grid; + int splitterIndex = int.MinValue; + + if (_gridCollapseDirection == GridCollapseOrientation.Rows) + { + // Get the index of the row containing the splitter + splitterIndex = (int)this.GetValue(Grid.RowProperty); + + // Determine the curent Direction + if (this.Direction == GridExpanderDirection.Next) + { + // Expand the next row + if (IsAnimated) + AnimateExpand(parentGrid.RowDefinitions[splitterIndex + 1]); + else + parentGrid.RowDefinitions[splitterIndex + 1].SetValue(RowDefinition.HeightProperty, _savedGridLength); + } + else + { + // Expand the previous row + if (IsAnimated) + AnimateExpand(parentGrid.RowDefinitions[splitterIndex - 1]); + else + parentGrid.RowDefinitions[splitterIndex - 1].SetValue(RowDefinition.HeightProperty, _savedGridLength); + } + } + else + { + // Get the index of the column containing the splitter + splitterIndex = (int)this.GetValue(Grid.ColumnProperty); + + // Determine the curent Direction + if (this.Direction == GridExpanderDirection.Next) + { + // Expand the next column + if (IsAnimated) + AnimateExpand(parentGrid.ColumnDefinitions[splitterIndex + 1]); + else + parentGrid.ColumnDefinitions[splitterIndex + 1].SetValue(ColumnDefinition.WidthProperty, _savedGridLength); + } + else + { + // Expand the previous column + if (IsAnimated) + AnimateExpand(parentGrid.ColumnDefinitions[splitterIndex - 1]); + else + parentGrid.ColumnDefinitions[splitterIndex - 1].SetValue(ColumnDefinition.WidthProperty, _savedGridLength); + } + } + } + + /// + /// Determine the collapse direction based on the horizontal and vertical alignments + /// + private GridCollapseOrientation GetCollapseDirection() + { + if (base.HorizontalAlignment != HorizontalAlignment.Stretch) + { + return GridCollapseOrientation.Columns; + } + + if ((base.VerticalAlignment == VerticalAlignment.Stretch) && (base.ActualWidth <= base.ActualHeight)) + { + return GridCollapseOrientation.Columns; + } + + return GridCollapseOrientation.Rows; + } + + /// + /// Handles the Checked event of either the Vertical or Horizontal + /// GridExpanderHandle ToggleButton. + /// + /// An instance of the ToggleButton that fired the event. + /// Contains event arguments for the routed event that fired. + private void GridExpanderButton_Checked(object sender, RoutedEventArgs e) + { + if (IsCollapsed != true) + { + // In our case, Checked = Collapsed. Which means we want everything + // ready to be expanded. + Collapse(); + + IsCollapsed = true; + + // Deactivate the background so the splitter can not be dragged. + _elementGridExpanderBackground.IsHitTestVisible = false; + //_elementGridExpanderBackground.Opacity = 0.5; + + // Raise the Collapsed event. + OnCollapsed(EventArgs.Empty); + } + } + + /// + /// Handles the Unchecked event of either the Vertical or Horizontal + /// GridExpanderHandle ToggleButton. + /// + /// An instance of the ToggleButton that fired the event. + /// Contains event arguments for the routed event that fired. + private void OnExpanderButtonUnchecked(object sender, RoutedEventArgs e) + { + if (IsCollapsed != false) + { + // In our case, Unchecked = Expanded. Which means we want everything + // ready to be collapsed. + Expand(); + + IsCollapsed = false; + + // Activate the background so the splitter can be dragged again. + _elementGridExpanderBackground.IsHitTestVisible = true; + //_elementGridExpanderBackground.Opacity = 1; + + // Raise the Expanded event. + OnExpanded(EventArgs.Empty); + } + } + + /// + /// The IsCollapsed property porperty changed handler. + /// + /// GridExpander that changed IsCollapsed. + /// An instance of DependencyPropertyChangedEventArgs. + private static void OnIsCollapsedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + GridExpander s = d as GridExpander; + + bool value = (bool)e.NewValue; + s.OnIsCollapsedChanged(value); + } + + /// + /// The DirectionProperty property changed handler. + /// + /// GridExpander that changed IsCollapsed. + /// An instance of DependencyPropertyChangedEventArgs. + private static void OnDirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + GridExpander s = d as GridExpander; + + GridExpanderDirection value = (GridExpanderDirection)e.NewValue; + s.OnDirectionChanged(value); + } + + private static void RowHeightAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + (d as GridExpander).AnimatingRow.Height = new GridLength((double)e.NewValue); + } + + private static void ColWidthAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + (d as GridExpander).AnimatingColumn.Width = new GridLength((double)e.NewValue); + } + + /// + /// Uses DoubleAnimation and a StoryBoard to animated the collapsing + /// of the specificed ColumnDefinition or RowDefinition. + /// + /// The RowDefinition or ColumnDefintition that will be collapsed. + private void AnimateCollapse(object definition) + { + double currentValue; + + // Setup the animation and StoryBoard + DoubleAnimation gridLengthAnimation = new DoubleAnimation() + { + Duration = new Duration(TimeSpan.FromMilliseconds(_animationTimeMillis)) + }; + Storyboard sb = new Storyboard(); + + // Add the animation to the StoryBoard + sb.Children.Add(gridLengthAnimation); + + if (_gridCollapseDirection == GridCollapseOrientation.Rows) + { + // Specify the target RowDefinition and property (Height) that will be altered by the animation. + this.AnimatingRow = (RowDefinition)definition; + Storyboard.SetTarget(gridLengthAnimation, this); + Storyboard.SetTargetProperty(gridLengthAnimation, new PropertyPath("RowHeightAnimation")); + + currentValue = AnimatingRow.ActualHeight; + } + else + { + // Specify the target ColumnDefinition and property (Width) that will be altered by the animation. + this.AnimatingColumn = (ColumnDefinition)definition; + Storyboard.SetTarget(gridLengthAnimation, this); + Storyboard.SetTargetProperty(gridLengthAnimation, new PropertyPath("ColWidthAnimation")); + + currentValue = AnimatingColumn.ActualWidth; + } + + gridLengthAnimation.From = currentValue; + gridLengthAnimation.To = 0; + + // Start the StoryBoard. + sb.Begin(); + } + + /// + /// Uses DoubleAnimation and a StoryBoard to animate the expansion + /// of the specificed ColumnDefinition or RowDefinition. + /// + /// The RowDefinition or ColumnDefintition that will be expanded. + private void AnimateExpand(object definition) + { + double currentValue; + + // Setup the animation and StoryBoard + DoubleAnimation gridLengthAnimation = new DoubleAnimation() + { + Duration = new Duration(TimeSpan.FromMilliseconds(_animationTimeMillis)) + }; + Storyboard sb = new Storyboard(); + + // Add the animation to the StoryBoard + sb.Children.Add(gridLengthAnimation); + + if (_gridCollapseDirection == GridCollapseOrientation.Rows) + { + // Specify the target RowDefinition and property (Height) that will be altered by the animation. + this.AnimatingRow = (RowDefinition)definition; + Storyboard.SetTarget(gridLengthAnimation, this); + Storyboard.SetTargetProperty(gridLengthAnimation, new PropertyPath("RowHeightAnimation")); + + currentValue = AnimatingRow.ActualHeight; + } + else + { + // Specify the target ColumnDefinition and property (Width) that will be altered by the animation. + this.AnimatingColumn = (ColumnDefinition)definition; + Storyboard.SetTarget(gridLengthAnimation, this); + Storyboard.SetTargetProperty(gridLengthAnimation, new PropertyPath("ColWidthAnimation")); + + currentValue = AnimatingColumn.ActualWidth; + } + gridLengthAnimation.From = currentValue; + gridLengthAnimation.To = _savedActualValue; + + // Start the StoryBoard. + sb.Begin(); + } + + #endregion + } +} diff --git a/Samples/WpfTestSvgControl/Images/Copy.svg b/Samples/WpfTestSvgControl/Images/Copy.svg new file mode 100644 index 000000000..3de60b008 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Cut.svg b/Samples/WpfTestSvgControl/Images/Cut.svg new file mode 100644 index 000000000..a28dbe156 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Cut.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Debug.svg b/Samples/WpfTestSvgControl/Images/Debug.svg new file mode 100644 index 000000000..18934c5ea --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Debug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Delete.svg b/Samples/WpfTestSvgControl/Images/Delete.svg new file mode 100644 index 000000000..9b9f54b78 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Find.svg b/Samples/WpfTestSvgControl/Images/Find.svg new file mode 100644 index 000000000..1b80bf68c --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Find.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/FolderClose.svg b/Samples/WpfTestSvgControl/Images/FolderClose.svg new file mode 100644 index 000000000..481bb01ac --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/FolderClose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/FolderOpen.svg b/Samples/WpfTestSvgControl/Images/FolderOpen.svg new file mode 100644 index 000000000..4c8a03f5b --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/FolderOpen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Format.svg b/Samples/WpfTestSvgControl/Images/Format.svg new file mode 100644 index 000000000..84ea7ccd0 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Format.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Information.svg b/Samples/WpfTestSvgControl/Images/Information.svg new file mode 100644 index 000000000..76647f248 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Information.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Number.svg b/Samples/WpfTestSvgControl/Images/Number.svg new file mode 100644 index 000000000..b2639d7da --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Open.svg b/Samples/WpfTestSvgControl/Images/Open.svg new file mode 100644 index 000000000..9d8f471f8 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/OpenFolder.svg b/Samples/WpfTestSvgControl/Images/OpenFolder.svg new file mode 100644 index 000000000..8284b8ba4 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/OpenFolder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Output.svg b/Samples/WpfTestSvgControl/Images/Output.svg new file mode 100644 index 000000000..8cdeaf18e --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Output.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/Images/Panning.svg b/Samples/WpfTestSvgControl/Images/Panning.svg new file mode 100644 index 000000000..313a82f10 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Panning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Paste.svg b/Samples/WpfTestSvgControl/Images/Paste.svg new file mode 100644 index 000000000..144e0ee5e --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Paste.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Print.svg b/Samples/WpfTestSvgControl/Images/Print.svg new file mode 100644 index 000000000..d7299a3df --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Print.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/PrintPreview.svg b/Samples/WpfTestSvgControl/Images/PrintPreview.svg new file mode 100644 index 000000000..2cb40960d --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/PrintPreview.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Redo.svg b/Samples/WpfTestSvgControl/Images/Redo.svg new file mode 100644 index 000000000..f19995258 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Redo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Run.svg b/Samples/WpfTestSvgControl/Images/Run.svg new file mode 100644 index 000000000..0713e1502 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Run.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Save.svg b/Samples/WpfTestSvgControl/Images/Save.svg new file mode 100644 index 000000000..e2a1dec56 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Settings.svg b/Samples/WpfTestSvgControl/Images/Settings.svg new file mode 100644 index 000000000..0a20698d6 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Space.svg b/Samples/WpfTestSvgControl/Images/Space.svg new file mode 100644 index 000000000..b05984d0c --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Space.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/SvgLogo.svg b/Samples/WpfTestSvgControl/Images/SvgLogo.svg new file mode 100644 index 000000000..ec4145531 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/SvgLogo.svg @@ -0,0 +1,64 @@ + + + SVG Logo + Designed for the SVG Logo Contest in 2006 by Harvey Rayner, and adopted by W3C in 2009. It is available under the Creative Commons license for those who have an SVG product or who are using SVG on their site. + + + + + SVG Logo + 14-08-2009 + + W3C + Harvey Rayner, designer + + See document description + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/Images/SvgLogoBasic.svg b/Samples/WpfTestSvgControl/Images/SvgLogoBasic.svg new file mode 100644 index 000000000..fc6213b54 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/SvgLogoBasic.svg @@ -0,0 +1,50 @@ + + + SVG Logo + Designed for the SVG Logo Contest in 2006 by Harvey Rayner, and adopted by W3C in 2009. It is available under the Creative Commons license for those who have an SVG product or who are using SVG on their site. + + + + + SVG Logo + 14-08-2009 + + W3C + Harvey Rayner, designer + + See document description + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/Images/Test.svg b/Samples/WpfTestSvgControl/Images/Test.svg new file mode 100644 index 000000000..59dd2aa87 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Test.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/TestResultDetail.svg b/Samples/WpfTestSvgControl/Images/TestResultDetail.svg new file mode 100644 index 000000000..0afc0c5a1 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/TestResultDetail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/TestRunner.svg b/Samples/WpfTestSvgControl/Images/TestRunner.svg new file mode 100644 index 000000000..915c81b46 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/TestRunner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Undo.svg b/Samples/WpfTestSvgControl/Images/Undo.svg new file mode 100644 index 000000000..b0fa32200 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Undo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/View.svg b/Samples/WpfTestSvgControl/Images/View.svg new file mode 100644 index 000000000..0a9671948 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/View.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/Web.svg b/Samples/WpfTestSvgControl/Images/Web.svg new file mode 100644 index 000000000..a888d5286 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/Web.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/WordWrap.svg b/Samples/WpfTestSvgControl/Images/WordWrap.svg new file mode 100644 index 000000000..1d03f87ca --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/WordWrap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/ZoomIn.svg b/Samples/WpfTestSvgControl/Images/ZoomIn.svg new file mode 100644 index 000000000..4741411e7 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/ZoomIn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/ZoomOut.svg b/Samples/WpfTestSvgControl/Images/ZoomOut.svg new file mode 100644 index 000000000..de7a749f1 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/ZoomOut.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/ZoomReset.svg b/Samples/WpfTestSvgControl/Images/ZoomReset.svg new file mode 100644 index 000000000..057870bd2 --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/ZoomReset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Images/ZoomToFit.svg b/Samples/WpfTestSvgControl/Images/ZoomToFit.svg new file mode 100644 index 000000000..9fac7c1ae --- /dev/null +++ b/Samples/WpfTestSvgControl/Images/ZoomToFit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/MainWindow.xaml b/Samples/WpfTestSvgControl/MainWindow.xaml new file mode 100644 index 000000000..f315f69e0 --- /dev/null +++ b/Samples/WpfTestSvgControl/MainWindow.xaml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/MainWindow.xaml.cs b/Samples/WpfTestSvgControl/MainWindow.xaml.cs new file mode 100644 index 000000000..d046fff52 --- /dev/null +++ b/Samples/WpfTestSvgControl/MainWindow.xaml.cs @@ -0,0 +1,1095 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Diagnostics; +using System.ComponentModel; +using System.Threading.Tasks; +using System.Collections.Generic; + +using System.Windows; +using System.Windows.Media; +using System.Windows.Input; +using System.Windows.Shapes; +using System.Windows.Resources; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; + +using Microsoft.Win32; + +using SharpVectors.Converters; +using SharpVectors.Renderers.Wpf; + +using IoPath = System.IO.Path; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + #region Private Fields + + private const int LeftPane = 350; + private const int LeftBottomPane = 300; + + private const string AppTitle = "SharpVectors: WPF Testing SVG"; + private const string AppErrorTitle = "SharpVectors: WPF Testing SVG - Error"; + private const string SvgTestSettings = "SvgTestSettings.xml"; + + private const string SvgFilePattern = "*.svg*"; + + private delegate void FileChangedToUIThread(FileSystemEventArgs e); + + private bool _leftSplitterChanging; + private bool _isBottomSplitterChanging; + + private string _drawingDir; + + private bool _isShown; + private bool _canDeleteXaml; + + private string _testSettingsPath; + private string _svgFilePath; + private string _xamlFilePath; + + private SvgPage _svgPage; + private XamlPage _xamlPage; + private DrawingPage _drawingPage; + private DebugPage _debugPage; + private SettingsPage _settingsPage; + + private ImageSource _folderClose; + private ImageSource _folderOpen; + private ImageSource _fileThumbnail; + + private OptionSettings _optionSettings; + + #endregion + + #region Constructors and Destructor + + public MainWindow() + { + InitializeComponent(); + + leftExpander.Expanded += OnLeftExpanderExpanded; + leftExpander.Collapsed += OnLeftExpanderCollapsed; + leftSplitter.MouseMove += OnLeftSplitterMove; + + bottomExpander.Expanded += OnBottomExpanderExpanded; + bottomExpander.Collapsed += OnBottomExpanderCollapsed; + bottomSplitter.MouseMove += OnBottomSplitterMove; + + this.Loaded += OnWindowLoaded; + this.Unloaded += OnWindowUnloaded; + this.Closing += OnWindowClosing; + + _drawingDir = IoPath.Combine(IoPath.GetDirectoryName( + System.Reflection.Assembly.GetExecutingAssembly().Location), DrawingPage.TemporalDirName); + + if (!Directory.Exists(_drawingDir)) + { + Directory.CreateDirectory(_drawingDir); + } + + _optionSettings = new OptionSettings(); + _testSettingsPath = IoPath.GetFullPath(SvgTestSettings); + if (!string.IsNullOrWhiteSpace(_testSettingsPath) && File.Exists(_testSettingsPath)) + { + _optionSettings.Load(_testSettingsPath); + // Override any saved local directory, default to sample files. + _optionSettings.CurrentSvgPath = _optionSettings.DefaultSvgPath; + } + + _optionSettings.PropertyChanged += OnSettingsPropertyChanged; + + try + { + _folderClose = this.GetImage(new Uri("Images/FolderClose.svg", UriKind.Relative)); + _folderOpen = this.GetImage(new Uri("Images/FolderOpen.svg", UriKind.Relative)); + _fileThumbnail = this.GetImage(new Uri("Images/SvgLogoBasic.svg", UriKind.Relative)); + } + catch (Exception ex) + { + _folderClose = null; + _folderOpen = null; + + MessageBox.Show(ex.ToString(), AppErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + #endregion + + #region Public Properties + + public OptionSettings OptionSettings + { + get { + return _optionSettings; + } + set { + if (value != null) + { + _optionSettings = value; + if (_drawingPage != null) + { + _drawingPage.ConversionSettings = value.ConversionSettings; + } + } + } + } + + #endregion + + #region Public Methods + + public async Task BrowseForFile() + { + OpenFileDialog dlg = new OpenFileDialog(); + dlg.Multiselect = false; + dlg.Title = "Select An SVG File"; + dlg.DefaultExt = "*.svg"; + dlg.Filter = "All SVG Files (*.svg,*.svgz)|*.svg;*.svgz" + + "|Svg Uncompressed Files (*.svg)|*.svg" + + "|SVG Compressed Files (*.svgz)|*.svgz"; + + bool? isSelected = dlg.ShowDialog(); + + if (isSelected != null && isSelected.Value) + { + this.CloseFile(); + + await this.LoadFile(dlg.FileName); + + TreeViewItem selItem = treeView.SelectedItem as TreeViewItem; + if (selItem == null || selItem.Tag == null) + { + return; + } + selItem.IsSelected = false; + } + } + + #endregion + + #region Protected Methods + + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + + double width = SystemParameters.PrimaryScreenWidth; + double height = SystemParameters.PrimaryScreenHeight; + + this.Width = Math.Min(1600, width) * 0.85; + this.Height = height * 0.85; + + this.Left = (width - this.Width) / 2.0; + this.Top = (height - this.Height) / 2.0; + + this.WindowStartupLocation = WindowStartupLocation.Manual; + + ColumnDefinition colExpander = mainGrid.ColumnDefinitions[0]; + colExpander.Width = new GridLength(LeftPane, GridUnitType.Pixel); + + RowDefinition rowExpander = bottomGrid.RowDefinitions[2]; + rowExpander.Height = new GridLength(LeftBottomPane, GridUnitType.Pixel); + } + + protected override void OnContentRendered(EventArgs e) + { + base.OnContentRendered(e); + + if (_isShown) + return; + + _isShown = true; + } + + #endregion + + #region Private Event Handlers + + private void OnWindowLoaded(object sender, RoutedEventArgs e) + { + bottomExpander.IsExpanded = true; + leftExpander.IsExpanded = true; + + // Retrieve the display pages... + _svgPage = frameSvgInput.Content as SvgPage; + _xamlPage = frameXamlOutput.Content as XamlPage; + _drawingPage = frameDrawing.Content as DrawingPage; + _debugPage = frameDebugging.Content as DebugPage; + _settingsPage = frameSettings.Content as SettingsPage; + + if (_svgPage != null) + { + _svgPage.MainWindow = this; + } + if (_xamlPage != null) + { + _xamlPage.MainWindow = this; + } + if (_drawingPage != null) + { + _drawingPage.WorkingDrawingDir = _drawingDir; + _drawingPage.MainWindow = this; + } + if (_debugPage != null) + { + _debugPage.MainWindow = this; + _debugPage.Startup(); + } + if (_settingsPage != null) + { + _settingsPage.MainWindow = this; + } + + tabSvgInput.Visibility = _optionSettings.ShowInputFile ? Visibility.Visible : Visibility.Collapsed; + tabXamlOutput.Visibility = _optionSettings.ShowOutputFile ? Visibility.Visible : Visibility.Collapsed; + } + + private void OnWindowUnloaded(object sender, RoutedEventArgs e) + { + } + + private void OnWindowClosing(object sender, CancelEventArgs e) + { + string backupFile = null; + if (File.Exists(_testSettingsPath)) + { + backupFile = IoPath.ChangeExtension(_testSettingsPath, SvgConverter.BackupExt); + try + { + if (File.Exists(backupFile)) + { + File.Delete(backupFile); + } + File.Move(_testSettingsPath, backupFile); + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), AppErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error); + + return; + } + } + try + { + _optionSettings.Save(_testSettingsPath); + } + catch (Exception ex) + { + if (File.Exists(backupFile)) + { + File.Move(backupFile, _testSettingsPath); + } + + MessageBox.Show(ex.ToString(), AppErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error); + } + if (!string.IsNullOrWhiteSpace(backupFile) && File.Exists(backupFile)) + { + File.Delete(backupFile); + } + + try + { + if (_canDeleteXaml && !string.IsNullOrWhiteSpace(_xamlFilePath) && File.Exists(_xamlFilePath)) + { + File.Delete(_xamlFilePath); + } + if (!string.IsNullOrWhiteSpace(_drawingDir) && Directory.Exists(_drawingDir)) + { + string[] imageFiles = Directory.GetFiles(_drawingDir, "*.png"); + if (imageFiles != null && imageFiles.Length != 0) + { + foreach (var imageFile in imageFiles) + { + File.Delete(imageFile); + } + } + } + } + catch (Exception ex) + { + Trace.TraceError(ex.ToString()); + } + + if (_debugPage != null) + { + _debugPage.Shutdown(); + } + } + + private async void OnBrowseForSvgFile(object sender, RoutedEventArgs e) + { + await this.BrowseForFile(); + } + + private void OnSettingsPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (!this.IsLoaded) + { + return; + } + + var changedProp = e.PropertyName; + if (string.IsNullOrWhiteSpace(changedProp)) + { + return; + } + + if (string.Equals(changedProp, "ShowInputFile", StringComparison.OrdinalIgnoreCase)) + { + this.OnFillSvgInputChecked(); + } + else if (string.Equals(changedProp, "ShowOutputFile", StringComparison.OrdinalIgnoreCase)) + { + this.OnFillXamlOutputChecked(); + } + } + + private void OnFillSvgInputChecked() + { + if (_svgPage == null) + { + tabSvgInput.Visibility = _optionSettings.ShowInputFile ? Visibility.Visible : Visibility.Collapsed; + return; + } + + Cursor saveCursor = this.Cursor; + + try + { + if (_optionSettings.ShowInputFile) + { + this.Cursor = Cursors.Wait; + this.ForceCursor = true; + + if (File.Exists(_svgFilePath)) + { + _svgPage.LoadDocument(_svgFilePath); + } + else + { + _svgPage.UnloadDocument(); + } + } + else + { + _svgPage.UnloadDocument(); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), AppErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + this.Cursor = saveCursor; + this.ForceCursor = false; + + tabSvgInput.Visibility = _optionSettings.ShowInputFile ? Visibility.Visible : Visibility.Collapsed; + } + } + + private void OnFillXamlOutputChecked() + { + if (_xamlPage == null || string.IsNullOrWhiteSpace(_xamlFilePath)) + { + tabXamlOutput.Visibility = _optionSettings.ShowOutputFile ? Visibility.Visible : Visibility.Collapsed; + return; + } + + Cursor saveCursor = this.Cursor; + + try + { + if (_optionSettings.ShowOutputFile) + { + this.Cursor = Cursors.Wait; + this.ForceCursor = true; + + if (!File.Exists(_xamlFilePath)) + { + if (!_drawingPage.SaveDocument(_xamlFilePath)) + { + return; + } + } + + if (File.Exists(_xamlFilePath)) + { + _xamlPage.LoadDocument(_xamlFilePath); + } + else + { + _xamlPage.UnloadDocument(); + } + } + else + { + _xamlPage.UnloadDocument(); + } + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), AppErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + this.Cursor = saveCursor; + this.ForceCursor = false; + + tabXamlOutput.Visibility = _optionSettings.ShowOutputFile ? Visibility.Visible : Visibility.Collapsed; + } + } + + private void OnTabItemGotFocus(object sender, RoutedEventArgs e) + { + if (sender == tabDrawing) + { + if (_drawingPage != null) + { + _drawingPage.PageSelected(true); + } + } + else if (sender == tabXamlOutput) + { + if (_xamlPage != null) + { + _xamlPage.PageSelected(true); + } + } + else if (sender == tabSvgInput) + { + if (_svgPage != null) + { + _svgPage.PageSelected(true); + } + } + else if (sender == tabSettings) + { + if (_settingsPage != null) + { + _settingsPage.PageSelected(true); + } + } + else if (sender == tabDebugging) + { + if (_debugPage != null) + { + _debugPage.PageSelected(true); + } + } + } + + #endregion + + #region TreeView Event Handlers + + private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + } + + private void OnTreeViewItemSelected(object sender, RoutedEventArgs e) + { + TreeViewItem selItem = treeView.SelectedItem as TreeViewItem; + if (selItem == null || selItem.Tag == null) + { + return; + } + + string selectedName = selItem.Tag as string; + if (string.IsNullOrWhiteSpace(selectedName)) + { + return; + } + + e.Handled = true; + + treeView.IsEnabled = false; + + try + { + this.Cursor = Cursors.Wait; + this.ForceCursor = true; + + _drawingPage.SelectElement(selectedName); + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), AppErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + this.Cursor = Cursors.Arrow; + this.ForceCursor = false; + + treeView.IsEnabled = true; + treeView.Focus(); + } + } + + private void OnTreeViewItemUnselected(object sender, RoutedEventArgs e) + { + } + + private void OnTreeViewItemCollapsed(object sender, RoutedEventArgs e) + { + if (_folderClose == null) + { + return; + } + + TreeViewItem treeItem = e.OriginalSource as TreeViewItem; + if (treeItem == null) + { + return; + } + + BulletDecorator decorator = treeItem.Header as BulletDecorator; + if (decorator == null) + { + return; + } + Image headerImage = decorator.Bullet as Image; + if (headerImage == null) + { + return; + } + headerImage.Source = _folderClose; + + e.Handled = true; + } + + private void OnTreeViewItemExpanded(object sender, RoutedEventArgs e) + { + if (_folderOpen == null) + { + return; + } + + TreeViewItem treeItem = e.OriginalSource as TreeViewItem; + if (treeItem == null) + { + return; + } + + BulletDecorator decorator = treeItem.Header as BulletDecorator; + if (decorator == null) + { + return; + } + Image headerImage = decorator.Bullet as Image; + if (headerImage == null) + { + return; + } + headerImage.Source = _folderOpen; + + e.Handled = true; + } + + #endregion + + #region Drag/Drop Methods + + private void OnDragEnter(object sender, DragEventArgs de) + { + if (de.Data.GetDataPresent(DataFormats.Text) || + de.Data.GetDataPresent(DataFormats.FileDrop)) + { + de.Effects = DragDropEffects.Copy; + } + else + { + de.Effects = DragDropEffects.None; + } + } + + private void OnDragLeave(object sender, DragEventArgs e) + { + + } + + private async void OnDragDrop(object sender, DragEventArgs de) + { + string fileName = ""; + if (de.Data.GetDataPresent(DataFormats.Text)) + { + fileName = (string)de.Data.GetData(DataFormats.Text); + } + else if (de.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] fileNames; + fileNames = (string[])de.Data.GetData(DataFormats.FileDrop); + fileName = fileNames[0]; + } + + if (!string.IsNullOrWhiteSpace(fileName)) + { + } + if (string.IsNullOrWhiteSpace(fileName) || !File.Exists(fileName)) + { + return; + } + + this.CloseFile(); + + try + { + this.Cursor = Cursors.Wait; + this.ForceCursor = true; + + await this.LoadFile(fileName); + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString(), AppErrorTitle, MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + this.Cursor = Cursors.Arrow; + this.ForceCursor = false; + } + } + + #endregion + + #region LeftExpander/Splitter Event Handlers + + private void OnLeftExpanderCollapsed(object sender, RoutedEventArgs e) + { + if (_leftSplitterChanging || _isBottomSplitterChanging) + { + return; + } + // Prevent WPF silly event routing... + if (e.Source != leftExpander) + { + return; + } + + e.Handled = true; + + ColumnDefinition columnDef = mainGrid.ColumnDefinitions[0]; + columnDef.Width = new GridLength(24, GridUnitType.Pixel); + } + + private void OnLeftExpanderExpanded(object sender, RoutedEventArgs e) + { + if (_leftSplitterChanging || _isBottomSplitterChanging) + { + return; + } + // Prevent WPF silly event routing... + if (e.Source != leftExpander) + { + return; + } + + e.Handled = true; + + ColumnDefinition columnDef = mainGrid.ColumnDefinitions[0]; + columnDef.Width = new GridLength(LeftPane, GridUnitType.Pixel); + } + + private void OnLeftSplitterMove(object sender, MouseEventArgs e) + { + _leftSplitterChanging = true; + + ColumnDefinition columnDef = mainGrid.ColumnDefinitions[0]; + + leftExpander.IsExpanded = columnDef.ActualWidth > 30; + + _leftSplitterChanging = false; + + e.Handled = true; + } + + #endregion + + #region BottomExpander/Splitter Event Handlers + + private void OnBottomExpanderCollapsed(object sender, RoutedEventArgs e) + { + if (_isBottomSplitterChanging) + { + return; + } + // Prevent WPF silly event routing... + if (e.Source != bottomExpander) + { + return; + } + + RowDefinition rowDef = bottomGrid.RowDefinitions[2]; + rowDef.Height = new GridLength(24, GridUnitType.Pixel); + } + + private void OnBottomExpanderExpanded(object sender, RoutedEventArgs e) + { + if (_isBottomSplitterChanging) + { + return; + } + // Prevent WPF silly event routing... + if (e.Source != bottomExpander) + { + return; + } + + RowDefinition rowDef = bottomGrid.RowDefinitions[2]; + rowDef.Height = new GridLength(LeftBottomPane, GridUnitType.Pixel); + } + + private void OnBottomSplitterMove(object sender, MouseEventArgs e) + { + _isBottomSplitterChanging = true; + + RowDefinition rowDef = bottomGrid.RowDefinitions[2]; + + bottomExpander.IsExpanded = rowDef.ActualHeight > 30; + + _isBottomSplitterChanging = false; + } + + #endregion + + #region Private Methods + + /// + /// This converts the SVG resource specified by the Uri to . + /// + /// A specifying the source of the SVG resource. + /// A of the converted SVG resource. + private DrawingGroup GetDrawing(Uri svgSource) + { + WpfDrawingSettings settings = new WpfDrawingSettings(); + settings.IncludeRuntime = false; + settings.TextAsGeometry = true; + settings.OptimizePath = true; + + StreamResourceInfo svgStreamInfo = null; + if (svgSource.ToString().IndexOf("siteoforigin", StringComparison.OrdinalIgnoreCase) >= 0) + { + svgStreamInfo = Application.GetRemoteStream(svgSource); + } + else + { + svgStreamInfo = Application.GetResourceStream(svgSource); + } + + Stream svgStream = (svgStreamInfo != null) ? svgStreamInfo.Stream : null; + + if (svgStream != null) + { + string fileExt = IoPath.GetExtension(svgSource.ToString()); + bool isCompressed = !string.IsNullOrWhiteSpace(fileExt) && string.Equals( + fileExt, SvgConverter.CompressedSvgExt, StringComparison.OrdinalIgnoreCase); + + if (isCompressed) + { + using (svgStream) + { + using (var zipStream = new GZipStream(svgStream, CompressionMode.Decompress)) + { + using (FileSvgReader reader = new FileSvgReader(settings)) + { + DrawingGroup drawGroup = reader.Read(zipStream); + + if (drawGroup != null) + { + return drawGroup; + } + } + } + } + } + else + { + using (svgStream) + { + using (FileSvgReader reader = new FileSvgReader(settings)) + { + DrawingGroup drawGroup = reader.Read(svgStream); + + if (drawGroup != null) + { + return drawGroup; + } + } + } + } + } + + return null; + } + + /// + /// This converts the SVG resource specified by the Uri to . + /// + /// A specifying the source of the SVG resource. + /// A of the converted SVG resource. + /// + /// This uses the method to convert the SVG resource to , + /// which is then wrapped in . + /// + private DrawingImage GetImage(Uri svgSource) + { + DrawingGroup drawGroup = this.GetDrawing(svgSource); + if (drawGroup != null) + { + return new DrawingImage(drawGroup); + } + return null; + } + + private async Task LoadFile(string fileName) + { + string fileExt = IoPath.GetExtension(fileName); + if (string.IsNullOrWhiteSpace(fileExt)) + { + return; + } + + bool generateXaml = _optionSettings.ShowOutputFile; + + if (string.Equals(fileExt, SvgConverter.SvgExt, StringComparison.OrdinalIgnoreCase) || + string.Equals(fileExt, SvgConverter.CompressedSvgExt, StringComparison.OrdinalIgnoreCase)) + { + _svgFilePath = fileName; + + if (_svgPage != null && _optionSettings.ShowInputFile) + { + _svgPage.LoadDocument(fileName); + } + + if (_drawingPage == null) + { + return; + } + _drawingPage.SaveXaml = generateXaml; + + try + { + if (await _drawingPage.LoadDocumentAsync(fileName)) + { + this.Title = AppTitle + " - " + IoPath.GetFileName(fileName); + + if (_xamlPage != null && !string.IsNullOrWhiteSpace(_drawingDir)) + { + string xamlFilePath = IoPath.Combine(_drawingDir, + IoPath.GetFileNameWithoutExtension(fileName) + SvgConverter.XamlExt); + + _xamlFilePath = xamlFilePath; + _canDeleteXaml = true; + + if (File.Exists(xamlFilePath) && _optionSettings.ShowOutputFile) + { + _xamlPage.LoadDocument(xamlFilePath); + } + } + + var drawingDocument = _drawingPage.DrawingDocument; + + this.FillTreeView(drawingDocument); + } + } + catch + { + // Try loading the XAML, if generated but the rendering failed... + if (_xamlPage != null && !string.IsNullOrWhiteSpace(_drawingDir)) + { + string xamlFilePath = IoPath.Combine(_drawingDir, + IoPath.GetFileNameWithoutExtension(fileName) + SvgConverter.XamlExt); + + _xamlFilePath = xamlFilePath; + _canDeleteXaml = true; + + if (File.Exists(xamlFilePath) && _optionSettings.ShowOutputFile) + { + _xamlPage.LoadDocument(xamlFilePath); + } + } + throw; + } + } + } + + private void CloseFile() + { + try + { + if (_svgPage != null) + { + _svgPage.UnloadDocument(); + } + if (_xamlPage != null) + { + _xamlPage.UnloadDocument(); + } + if (_drawingPage != null) + { + _drawingPage.UnloadDocument(); + } + + if (_canDeleteXaml && !string.IsNullOrWhiteSpace(_xamlFilePath) && File.Exists(_xamlFilePath)) + { + File.Delete(_xamlFilePath); + } + if (!string.IsNullOrWhiteSpace(_drawingDir) && Directory.Exists(_drawingDir)) + { + string[] imageFiles = Directory.GetFiles(_drawingDir, "*.png"); + if (imageFiles != null && imageFiles.Length != 0) + { + try + { + foreach (var imageFile in imageFiles) + { + if (File.Exists(imageFile)) + { + File.Delete(imageFile); + } + } + } + catch (IOException ex) + { + Trace.TraceError(ex.ToString()); + // Image this, WPF will typically cache and/or lock loaded images + } + } + } + + _svgFilePath = null; + _xamlFilePath = null; + _canDeleteXaml = false; + } + catch (Exception ex) + { + Trace.TraceError(ex.ToString()); + } + } + + #region FillTreeView Methods + + private void FillTreeView(WpfDrawingDocument drawingDocument) + { + if (drawingDocument == null) + { + return; + } + + treeView.BeginInit(); + treeView.Items.Clear(); + + for (int i = 0; i < 1; i++) + { + TextBlock headerText = new TextBlock(); + headerText.Text = i == 0 ? "SVG Element Names" : "SVG Element Unique Names"; + headerText.Margin = new Thickness(3, 0, 0, 0); + + BulletDecorator decorator = new BulletDecorator(); + if (_folderClose != null) + { + Image image = new Image(); + image.Source = _folderClose; + + decorator.Bullet = image; + } + else + { + Ellipse bullet = new Ellipse(); + bullet.Height = 16; + bullet.Width = 16; + bullet.Fill = Brushes.Goldenrod; + bullet.Stroke = Brushes.DarkGray; + bullet.StrokeThickness = 1; + + decorator.Bullet = bullet; + } + decorator.Margin = new Thickness(0, 0, 10, 0); + decorator.Child = headerText; + + TreeViewItem categoryItem = new TreeViewItem(); + categoryItem.Tag = string.Empty; + categoryItem.Header = decorator; + categoryItem.Margin = new Thickness(0); + categoryItem.Padding = new Thickness(3); + categoryItem.FontSize = 14; + categoryItem.FontWeight = FontWeights.Bold; + + treeView.Items.Add(categoryItem); + + // FillTreeView(i == 0 ? drawingDocument.ElementNames : drawingDocument.ElementUniqueNames, categoryItem); + FillTreeView(i == 0 ? drawingDocument.DrawingNames : drawingDocument.DrawingUniqueNames, categoryItem); + + categoryItem.IsExpanded = (i == 0); + } + + treeView.EndInit(); + + leftExpander.IsExpanded = true; + bottomExpander.IsExpanded = true; + } + + private void FillTreeView(ICollection idItems, TreeViewItem treeItem) + { + if (idItems == null || idItems.Count == 0) + { + return; + } + + int itemCount = 0; + + foreach (var idItem in idItems) + { + TextBlock itemText = new TextBlock(); + itemText.Text = string.Format("({0:D3}) - {1}", itemCount, idItem); + itemText.Margin = new Thickness(3, 0, 0, 0); + + BulletDecorator fileItem = new BulletDecorator(); + if (_fileThumbnail != null) + { + Image image = new Image(); + image.Source = _fileThumbnail; + image.Height = 16; + image.Width = 16; + + fileItem.Bullet = image; + } + else + { + Ellipse bullet = new Ellipse(); + bullet.Height = 16; + bullet.Width = 16; + bullet.Fill = Brushes.Goldenrod; + bullet.Stroke = Brushes.DarkGray; + bullet.StrokeThickness = 1; + + fileItem.Bullet = bullet; + } + fileItem.Margin = new Thickness(0, 0, 10, 0); + fileItem.Child = itemText; + + TreeViewItem item = new TreeViewItem(); + item.Tag = idItem; + item.Header = fileItem; + item.Margin = new Thickness(0); + item.Padding = new Thickness(2); + item.FontSize = 12; + item.FontWeight = FontWeights.Normal; + + treeItem.Items.Add(item); + + itemCount++; + } + } + + #endregion + + #endregion + } +} diff --git a/Samples/WpfTestSvgControl/MainWindowSettings.cs b/Samples/WpfTestSvgControl/MainWindowSettings.cs new file mode 100644 index 000000000..27a4f6e97 --- /dev/null +++ b/Samples/WpfTestSvgControl/MainWindowSettings.cs @@ -0,0 +1,304 @@ +// The codes by Jake Ginnivan and licensed under MIT. +// Web Link: http://jake.ginnivan.net/remembering-wpf-window-positions +// + +using System; +using System.Diagnostics; +using System.Configuration; +using System.ComponentModel; +using System.Runtime.InteropServices; + +using System.Windows; +using System.Windows.Interop; + +namespace WpfTestSvgControl +{ + /// + /// Persists a Window's Size, Location and WindowState to UserScopeSettings + /// + public sealed class MainWindowSettings + { + [DllImport("user32.dll")] + private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl); + + [DllImport("user32.dll")] + private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl); + + // ReSharper disable InconsistentNaming + private const int SW_SHOWNORMAL = 1; + private const int SW_SHOWMINIMIZED = 2; + // ReSharper restore InconsistentNaming + + private Window _window; + + private WindowApplicationSettings _windowApplicationSettings; + + public MainWindowSettings(Window window) + { + _window = window; + } + + /// + /// Register the "Save" attached property and the "OnSaveInvalidated" callback + /// + public static readonly DependencyProperty SaveProperty + = DependencyProperty.RegisterAttached("Save", typeof(bool), typeof(MainWindowSettings), + new FrameworkPropertyMetadata(new PropertyChangedCallback(OnSaveInvalidated))); + + + public static void SetSave(DependencyObject dependencyObject, bool enabled) + { + dependencyObject.SetValue(SaveProperty, enabled); + } + + /// + /// Called when Save is changed on an object. + /// + private static void OnSaveInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + { + var window = dependencyObject as Window; + if (window == null || !((bool)e.NewValue)) + return; + var settings = new MainWindowSettings(window); + settings.Attach(); + } + + /// + /// Load the Window Size Location and State from the settings object + /// + private void LoadWindowState() + { + Settings.Reload(); + + if (Settings.Placement == null) + return; + try + { + // Load window placement details for previous application session from application settings + // if window was closed on a monitor that is now disconnected from the computer, + // SetWindowPlacement will place the window onto a visible monitor. + var wp = Settings.Placement.Value; + + wp.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT)); + wp.flags = 0; + wp.showCmd = (wp.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : wp.showCmd); + var hwnd = new WindowInteropHelper(_window).Handle; + SetWindowPlacement(hwnd, ref wp); + } + catch (Exception ex) + { + Trace.TraceError(ex.ToString()); + } + } + + /// + /// Save the Window Size, Location and State to the settings object + /// + private void SaveWindowState() + { + WINDOWPLACEMENT wp; + var hwnd = new WindowInteropHelper(_window).Handle; + GetWindowPlacement(hwnd, out wp); + Settings.Placement = wp; + Settings.Save(); + } + + private void Attach() + { + if (_window == null) + return; + _window.Closing += WindowClosing; + _window.SourceInitialized += WindowSourceInitialized; + } + + private void WindowSourceInitialized(object sender, EventArgs e) + { + LoadWindowState(); + } + + private void WindowClosing(object sender, CancelEventArgs e) + { + SaveWindowState(); + _window.Closing -= WindowClosing; + _window.SourceInitialized -= WindowSourceInitialized; + _window = null; + } + + internal WindowApplicationSettings CreateWindowApplicationSettingsInstance() + { + return new WindowApplicationSettings(this); + } + + [Browsable(false)] + internal WindowApplicationSettings Settings + { + get { + if (_windowApplicationSettings == null) + { + _windowApplicationSettings = CreateWindowApplicationSettingsInstance(); + } + return _windowApplicationSettings; + } + } + + internal class WindowApplicationSettings : ApplicationSettingsBase + { + public WindowApplicationSettings(MainWindowSettings windowSettings) + : base(windowSettings._window.GetType().FullName) + { + } + + [UserScopedSetting] + public WINDOWPLACEMENT? Placement + { + get { + if (this["Placement"] != null) + { + return ((WINDOWPLACEMENT)this["Placement"]); + } + return null; + } + set { + this["Placement"] = value; + } + } + } + } + + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + private int _left; + private int _top; + private int _right; + private int _bottom; + + public RECT(int left, int top, int right, int bottom) + { + _left = left; + _top = top; + _right = right; + _bottom = bottom; + } + + public override bool Equals(object obj) + { + if (obj is RECT) + { + var rect = (RECT)obj; + + return rect._bottom == _bottom && + rect._left == _left && + rect._right == _right && + rect._top == _top; + } + return base.Equals(obj); + } + + public override int GetHashCode() + { + return this.Bottom.GetHashCode() ^ this.Left.GetHashCode() ^ this.Right.GetHashCode() ^ this.Top.GetHashCode(); + } + + public static bool operator ==(RECT a, RECT b) + { + return a._bottom == b._bottom && + a._left == b._left && + a._right == b._right && + a._top == b._top; + } + + public static bool operator !=(RECT a, RECT b) + { + return !(a == b); + } + + public int Left + { + get { return _left; } + set { _left = value; } + } + + public int Top + { + get { return _top; } + set { _top = value; } + } + + public int Right + { + get { return _right; } + set { _right = value; } + } + + public int Bottom + { + get { return _bottom; } + set { _bottom = value; } + } + } + + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + private int _x; + private int _y; + + public POINT(int x, int y) + { + _x = x; + _y = y; + } + + public int X + { + get { return _x; } + set { _x = value; } + } + + public int Y + { + get { return _y; } + set { _y = value; } + } + + public override bool Equals(object obj) + { + if (obj is POINT) + { + var point = (POINT)obj; + + return point._x == _x && point._y == _y; + } + return base.Equals(obj); + } + public override int GetHashCode() + { + return this.X.GetHashCode() ^ this.Y.GetHashCode(); + } + + public static bool operator ==(POINT a, POINT b) + { + return a._x == b._x && a._y == b._y; + } + + public static bool operator !=(POINT a, POINT b) + { + return !(a == b); + } + } + + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct WINDOWPLACEMENT + { + public int length; + public int flags; + public int showCmd; + public POINT minPosition; + public POINT maxPosition; + public RECT normalPosition; + } +} diff --git a/Samples/WpfTestSvgControl/OptionSettings.cs b/Samples/WpfTestSvgControl/OptionSettings.cs new file mode 100644 index 000000000..22dc13cab --- /dev/null +++ b/Samples/WpfTestSvgControl/OptionSettings.cs @@ -0,0 +1,624 @@ +using System; +using System.IO; +using System.Xml; +using System.Text; +using System.Linq; +using System.ComponentModel; +using System.Runtime.InteropServices; + +using SharpVectors.Renderers.Wpf; + +namespace WpfTestSvgControl +{ + [Serializable] + public sealed class OptionSettings : INotifyPropertyChanged, ICloneable + { + #region Private Interop Methods + + [DllImport("shell32.dll", SetLastError = true)] + private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, + uint cidl, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, uint dwFlags); + + [DllImport("shell32.dll", SetLastError = true)] + private static extern void SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string name, + IntPtr bindingContext, [Out] out IntPtr pidl, uint sfgaoIn, [Out] out uint psfgaoOut); + + #endregion + + #region Public Events + + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + #region Private Fields + + private const string ParentSymbol = "..\\"; + private const string SharpVectors = "SharpVectors"; + + [DllImport("Shlwapi.dll", EntryPoint = "PathIsDirectoryEmpty")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsDirectoryEmpty([MarshalAs(UnmanagedType.LPStr)]string directory); + + private bool _hidePathsRoot; + private bool _showInputFile; + private bool _showOutputFile; + private bool _recursiveSearch; + + private string _defaultSvgPath; + private string _currentSvgPath; + + private string _selectedValuePath; + + private WpfDrawingSettings _wpfSettings; + + #endregion + + #region Constructors and Destructor + + public OptionSettings() + { + _wpfSettings = new WpfDrawingSettings(); + string currentDir = Path.GetFullPath(@".\Samples"); + if (!Directory.Exists(currentDir)) + { + Directory.CreateDirectory(currentDir); + } + _currentSvgPath = currentDir; + _defaultSvgPath = currentDir; + + _showInputFile = false; + _showOutputFile = false; + _recursiveSearch = true; + } + + public OptionSettings(WpfDrawingSettings wpfSettings, string testPath) + { + _wpfSettings = wpfSettings; + _currentSvgPath = testPath; + + _showInputFile = false; + _showOutputFile = false; + _recursiveSearch = true; + + if (wpfSettings == null) + { + _wpfSettings = new WpfDrawingSettings(); + } + if (string.IsNullOrWhiteSpace(testPath)) + { + string currentDir = Path.GetFullPath(@".\Samples"); + _currentSvgPath = currentDir; + } + if (!Directory.Exists(_currentSvgPath)) + { + Directory.CreateDirectory(_currentSvgPath); + } + _defaultSvgPath = _currentSvgPath; + } + + public OptionSettings(OptionSettings source) + { + if (source == null) + { + return; + } + _hidePathsRoot = source._hidePathsRoot; + _defaultSvgPath = source._defaultSvgPath; + _currentSvgPath = source._currentSvgPath; + _showInputFile = source._showInputFile; + _showOutputFile = source._showOutputFile; + _recursiveSearch = source._recursiveSearch; + _wpfSettings = source._wpfSettings; + } + + #endregion + + #region Public Properties + + public bool HidePathsRoot + { + get { + return _hidePathsRoot; + } + set { + bool isChanged = (_hidePathsRoot != value); + _hidePathsRoot = value; + + if (isChanged) + { + this.RaisePropertyChanged("HidePathsRoot"); + } + } + } + + public bool ShowInputFile + { + get { + return _showInputFile; + } + set { + bool isChanged = (_showInputFile != value); + _showInputFile = value; + + if (isChanged) + { + this.RaisePropertyChanged("ShowInputFile"); + } + } + } + + public bool ShowOutputFile + { + get { + return _showOutputFile; + } + set { + bool isChanged = (_showOutputFile != value); + _showOutputFile = value; + + if (isChanged) + { + this.RaisePropertyChanged("ShowOutputFile"); + } + } + } + + public bool RecursiveSearch + { + get { + return _recursiveSearch; + } + set { + bool isChanged = (_recursiveSearch != value); + _recursiveSearch = value; + + if (isChanged) + { + this.RaisePropertyChanged("RecursiveSearch"); + } + } + } + + public string DefaultSvgPath + { + get { + return _defaultSvgPath; + } + set { + bool isChanged = !string.Equals(_defaultSvgPath, value, StringComparison.OrdinalIgnoreCase); + _defaultSvgPath = value; + + if (isChanged) + { + this.RaisePropertyChanged("DefaultSvgPath"); + } + } + } + + public string CurrentSvgPath + { + get { + return _currentSvgPath; + } + set { + bool isChanged = !string.Equals(_currentSvgPath, value, StringComparison.OrdinalIgnoreCase); + _currentSvgPath = value; + + if (isChanged) + { + this.RaisePropertyChanged("CurrentSvgPath"); + } + } + } + + public string SelectedValuePath + { + get { + return _selectedValuePath; + } + set { + bool isChanged = !string.Equals(_defaultSvgPath, value, StringComparison.OrdinalIgnoreCase); + _selectedValuePath = value; + + if (isChanged) + { + this.RaisePropertyChanged("SelectedValuePath"); + } + } + } + + public WpfDrawingSettings ConversionSettings + { + get { + return _wpfSettings; + } + set { + if (value != null) + { + bool isChanged = (_wpfSettings != value); + + _wpfSettings = value; + + if (isChanged) + { + this.RaisePropertyChanged("ConversionSettings"); + } + } + } + } + + #endregion + + #region Public Methods + + public string GetPath(string inputPath) + { + if (string.IsNullOrWhiteSpace(inputPath)) + { + return inputPath; + } + if (_hidePathsRoot) + { + Uri fullPath = new Uri(inputPath, UriKind.Absolute); + + // Make relative path to the SharpVectors folder... + int indexOf = inputPath.IndexOf(SharpVectors, StringComparison.OrdinalIgnoreCase); + if (indexOf > 0) + { + Uri relRoot = new Uri(inputPath.Substring(0, indexOf), UriKind.Absolute); + + string relPath = relRoot.MakeRelativeUri(fullPath).ToString(); + relPath = relPath.Replace('/', '\\'); + + relPath = Uri.UnescapeDataString(relPath); + if (!relPath.StartsWith(ParentSymbol, StringComparison.OrdinalIgnoreCase)) + { + relPath = ParentSymbol + relPath; + } + + return relPath; + } + } + return inputPath; + } + + public void Load(string settingsPath) + { + if (string.IsNullOrWhiteSpace(settingsPath) || + File.Exists(settingsPath) == false) + { + return; + } + + XmlReaderSettings settings = new XmlReaderSettings(); + settings.IgnoreWhitespace = false; + settings.IgnoreComments = true; + settings.IgnoreProcessingInstructions = true; + + using (XmlReader reader = XmlReader.Create(settingsPath, settings)) + { + this.Load(reader); + } + } + + public void Save(string settingsPath) + { + if (string.IsNullOrWhiteSpace(settingsPath)) + { + return; + } + + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.IndentChars = " "; + settings.Encoding = Encoding.UTF8; + + using (XmlWriter writer = XmlWriter.Create(settingsPath, settings)) + { + this.Save(writer); + } + } + + public bool IsCurrentSvgPathChanged(string svgPath) + { + if (string.IsNullOrWhiteSpace(svgPath) || + string.IsNullOrWhiteSpace(_currentSvgPath)) + { + return true; + } + string currentPath = string.Copy(svgPath); + if (!currentPath.EndsWith("\\", StringComparison.OrdinalIgnoreCase)) + { + currentPath = currentPath + "\\"; + } + + string currentSvgPath = string.Copy(_currentSvgPath); + if (!_currentSvgPath.EndsWith("\\", StringComparison.OrdinalIgnoreCase)) + { + currentSvgPath = _currentSvgPath + "\\"; + } + + return !(string.Equals(currentPath, currentSvgPath, StringComparison.OrdinalIgnoreCase)); + } + + public static void OpenFolderAndSelectItem(string folderPath, string file) + { + if (string.IsNullOrEmpty(folderPath) || Directory.Exists(folderPath) == false) + { + return; + } + + if (string.IsNullOrWhiteSpace(file)) + { + var selectedIsFile = false; + var selectedName = string.Empty; + var dirInfo = new DirectoryInfo(folderPath); + + var firstFileName = dirInfo.EnumerateFiles() + .Select(f => f.Name) + .FirstOrDefault(name => !string.Equals(name, "Thumbs.db", StringComparison.OrdinalIgnoreCase)); + if (!string.IsNullOrWhiteSpace(firstFileName)) + { + selectedIsFile = true; + selectedName = firstFileName; + } + else + { + if (!IsDirectoryEmpty(dirInfo.FullName)) + { + var firstDirName = dirInfo.EnumerateDirectories() + .Select(f => f.Name) + .FirstOrDefault(); + if (!string.IsNullOrWhiteSpace(firstDirName)) + { + selectedIsFile = false; + selectedName = firstDirName; + } + } + } + if (!string.IsNullOrWhiteSpace(selectedName)) + { + if (selectedIsFile) + { + file = selectedName; + } + else + { + folderPath = Path.Combine(folderPath, selectedName); + } + } + } + + IntPtr nativeFolder; + uint psfgaoOut; + SHParseDisplayName(folderPath, IntPtr.Zero, out nativeFolder, 0, out psfgaoOut); + + if (nativeFolder == IntPtr.Zero) + { + // Log error, can't find folder + return; + } + + IntPtr nativeFile = IntPtr.Zero; + if (!string.IsNullOrWhiteSpace(file)) + { + SHParseDisplayName(Path.Combine(folderPath, file), + IntPtr.Zero, out nativeFile, 0, out psfgaoOut); + } + + IntPtr[] fileArray; + if (nativeFile == IntPtr.Zero) + { + // Open the folder without the file selected if we can't find the file + fileArray = new IntPtr[0]; + } + else + { + fileArray = new IntPtr[] { nativeFile }; + } + + SHOpenFolderAndSelectItems(nativeFolder, (uint)fileArray.Length, fileArray, 0); + + Marshal.FreeCoTaskMem(nativeFolder); + if (nativeFile != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(nativeFile); + } + } + + #endregion + + #region Private Methods + + private void RaisePropertyChanged(string propertyName) + { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private void Load(XmlReader reader) + { + var comparer = StringComparison.OrdinalIgnoreCase; + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && + string.Equals(reader.Name, "option", comparer)) + { + string optionName = reader.GetAttribute("name"); + string optionType = reader.GetAttribute("type"); + if (string.Equals(optionType, "String", comparer)) + { + string optionValue = reader.ReadElementContentAsString(); + + switch (optionName) + { + case "DefaultSvgPath": + if (optionValue.StartsWith(ParentSymbol, comparer)) + { + var inputPath = string.Copy(_defaultSvgPath); + int indexOf = inputPath.IndexOf(SharpVectors, comparer); + + if (indexOf > 0) + { + var basePath = inputPath.Substring(0, indexOf); + _defaultSvgPath = Path.Combine(basePath, optionValue.Replace(ParentSymbol, "")); + } + else + { + _defaultSvgPath = optionValue; + } + } + else + { + _defaultSvgPath = optionValue; + } + break; + case "CurrentSvgPath": + if (optionValue.StartsWith(ParentSymbol, comparer)) + { + var inputPath = string.Copy(_currentSvgPath); + int indexOf = inputPath.IndexOf(SharpVectors, comparer); + + if (indexOf > 0) + { + var basePath = inputPath.Substring(0, indexOf); + _currentSvgPath = Path.Combine(basePath, optionValue.Replace(ParentSymbol, "")); + } + else + { + _currentSvgPath = optionValue; + } + } + else + { + _currentSvgPath = optionValue; + } + break; + case "SelectedValuePath": + _selectedValuePath = optionValue; + break; + } + } + else if (string.Equals(optionType, "Boolean", comparer)) + { + bool optionValue = reader.ReadElementContentAsBoolean(); + switch (optionName) + { + case "HidePathsRoot": + _hidePathsRoot = optionValue; + break; + case "ShowInputFile": + _showInputFile = optionValue; + break; + case "ShowOutputFile": + _showOutputFile = optionValue; + break; + case "RecursiveSearch": + _recursiveSearch = optionValue; + break; + + case "TextAsGeometry": + _wpfSettings.TextAsGeometry = optionValue; + break; + case "IncludeRuntime": + _wpfSettings.IncludeRuntime = optionValue; + break; + + case "IgnoreRootViewbox": + _wpfSettings.IgnoreRootViewbox = optionValue; + break; + case "EnsureViewboxSize": + _wpfSettings.EnsureViewboxSize = optionValue; + break; + case "EnsureViewboxPosition": + _wpfSettings.EnsureViewboxPosition = optionValue; + break; + } + } + + } + } + } + + private void Save(XmlWriter writer) + { + writer.WriteStartDocument(); + writer.WriteStartElement("options"); + + this.SaveOption(writer, "HidePathsRoot", _hidePathsRoot); + this.SaveOption(writer, "ShowInputFile", _showInputFile); + this.SaveOption(writer, "ShowOutputFile", _showOutputFile); + this.SaveOption(writer, "RecursiveSearch", _recursiveSearch); + this.SaveOption(writer, "DefaultSvgPath", this.GetPath(_defaultSvgPath)); + this.SaveOption(writer, "CurrentSvgPath", this.GetPath(_currentSvgPath)); + this.SaveOption(writer, "SelectedValuePath", _selectedValuePath); + + if (_wpfSettings != null) + { + this.SaveOption(writer, "TextAsGeometry", _wpfSettings.TextAsGeometry); + this.SaveOption(writer, "IncludeRuntime", _wpfSettings.IncludeRuntime); + + this.SaveOption(writer, "IgnoreRootViewbox", _wpfSettings.IgnoreRootViewbox); + this.SaveOption(writer, "EnsureViewboxSize", _wpfSettings.EnsureViewboxSize); + this.SaveOption(writer, "EnsureViewboxPosition", _wpfSettings.EnsureViewboxPosition); + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + private void SaveOption(XmlWriter writer, string name, string value) + { + if (value == null) + { + value = string.Empty; + } + + writer.WriteStartElement("option"); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("type", "String"); + writer.WriteString(value); + writer.WriteEndElement(); + } + private void SaveOption(XmlWriter writer, string name, bool value) + { + writer.WriteStartElement("option"); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("type", "Boolean"); + writer.WriteString(value ? "true" : "false"); + writer.WriteEndElement(); + } + + #endregion + + #region ICloneable Members + + public OptionSettings Clone() + { + OptionSettings optSettings = new OptionSettings(this); + + if (_wpfSettings != null) + { + optSettings._wpfSettings = _wpfSettings.Clone(); + } + if (_defaultSvgPath != null) + { + optSettings._defaultSvgPath = string.Copy(_defaultSvgPath); + } + if (_currentSvgPath != null) + { + optSettings._currentSvgPath = string.Copy(_currentSvgPath); + } + + return optSettings; + } + + object ICloneable.Clone() + { + return this.Clone(); + } + + #endregion + } +} diff --git a/Samples/WpfTestSvgControl/PrintPreviewWindow.xaml b/Samples/WpfTestSvgControl/PrintPreviewWindow.xaml new file mode 100644 index 000000000..908642dc9 --- /dev/null +++ b/Samples/WpfTestSvgControl/PrintPreviewWindow.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/Samples/WpfTestSvgControl/PrintPreviewWindow.xaml.cs b/Samples/WpfTestSvgControl/PrintPreviewWindow.xaml.cs new file mode 100644 index 000000000..1909469ba --- /dev/null +++ b/Samples/WpfTestSvgControl/PrintPreviewWindow.xaml.cs @@ -0,0 +1,165 @@ +using System; +using System.IO; +using System.IO.Packaging; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.ComponentModel; +using System.Collections.Generic; + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using System.Windows.Xps.Packaging; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for PrintPreviewWindow.xaml + /// + public partial class PrintPreviewWindow : Window + { + #region Private Fields + + private string _fileName; + private Package _xpsDocPackage; + private XpsDocument _xpsDocument; + + #endregion + + #region Constructors and Destructor + + public PrintPreviewWindow() + { + InitializeComponent(); + + this.Loaded += new RoutedEventHandler(OnWindowLoaded); + this.Unloaded += new RoutedEventHandler(OnWindowUnloaded); + + this.Closed += new EventHandler(OnWindowClosed); + this.Closing += new CancelEventHandler(OnWindowClosing); + } + + #endregion + + #region Public Methods + + public void LoadDocument(XpsDocument document, Package package, string sourceFileName) + { + if (document == null) + { + return; + } + + try + { + if (_xpsDocument != null) + { + _xpsDocument.Close(); + _xpsDocument = null; + } + + if (_xpsDocPackage != null) + { + _xpsDocPackage.Close(); + _xpsDocPackage = null; + } + + if (!string.IsNullOrWhiteSpace(_fileName)) + { + PackageStore.RemovePackage(new Uri(_fileName)); + } + } + catch + { + } + + _xpsDocument = document; + _xpsDocPackage = package; + _fileName = sourceFileName; + + docViewer.Document = _xpsDocument.GetFixedDocumentSequence(); + } + + #endregion + + #region Private Methods + + private void OnWindowLoaded(object sender, RoutedEventArgs e) + { + ContentControl findToolbar = docViewer.Template.FindName( + "PART_FindToolBarHost", docViewer) as ContentControl; + if (findToolbar != null) + { + findToolbar.Visibility = Visibility.Collapsed; + } + } + + private void OnWindowUnloaded(object sender, RoutedEventArgs e) + { + try + { + docViewer.Document = null; + + if (_xpsDocument != null) + { + _xpsDocument.Close(); + _xpsDocument = null; + } + + if (_xpsDocPackage != null) + { + _xpsDocPackage.Close(); + _xpsDocPackage = null; + } + + if (!string.IsNullOrWhiteSpace(_fileName)) + { + PackageStore.RemovePackage(new Uri(_fileName)); + } + } + catch + { + } + } + + private void OnWindowClosing(object sender, CancelEventArgs e) + { + try + { + docViewer.Document = null; + + if (_xpsDocument != null) + { + _xpsDocument.Close(); + _xpsDocument = null; + } + + if (_xpsDocPackage != null) + { + _xpsDocPackage.Close(); + _xpsDocPackage = null; + } + + if (!string.IsNullOrWhiteSpace(_fileName)) + { + PackageStore.RemovePackage(new Uri(_fileName)); + } + } + catch + { + } + } + + private void OnWindowClosed(object sender, EventArgs e) + { + } + + #endregion + } +} diff --git a/Samples/WpfTestSvgControl/Properties/AssemblyInfo.cs b/Samples/WpfTestSvgControl/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1019508dc --- /dev/null +++ b/Samples/WpfTestSvgControl/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WpfTestSvgControl")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WpfTestSvgControl")] +[assembly: AssemblyCopyright("Copyright © 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.3.0.0")] +[assembly: AssemblyFileVersion("1.3.0.0")] diff --git a/Samples/WpfTestSvgControl/Properties/Resources.Designer.cs b/Samples/WpfTestSvgControl/Properties/Resources.Designer.cs new file mode 100644 index 000000000..778c4e9cb --- /dev/null +++ b/Samples/WpfTestSvgControl/Properties/Resources.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WpfTestSvgControl.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfTestSvgControl.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] PanTool { + get { + object obj = ResourceManager.GetObject("PanTool", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] PanToolDown { + get { + object obj = ResourceManager.GetObject("PanToolDown", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/Samples/WpfTestSvgControl/Properties/Resources.resx b/Samples/WpfTestSvgControl/Properties/Resources.resx new file mode 100644 index 000000000..9568416da --- /dev/null +++ b/Samples/WpfTestSvgControl/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\PanTool.cur;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\PanToolDown.cur;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Properties/Settings.Designer.cs b/Samples/WpfTestSvgControl/Properties/Settings.Designer.cs new file mode 100644 index 000000000..8eaca5753 --- /dev/null +++ b/Samples/WpfTestSvgControl/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WpfTestSvgControl.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Samples/WpfTestSvgControl/Properties/Settings.settings b/Samples/WpfTestSvgControl/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Samples/WpfTestSvgControl/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/Resources/PanTool.cur b/Samples/WpfTestSvgControl/Resources/PanTool.cur new file mode 100644 index 000000000..6d10c848c Binary files /dev/null and b/Samples/WpfTestSvgControl/Resources/PanTool.cur differ diff --git a/Samples/WpfTestSvgControl/Resources/PanToolDown.cur b/Samples/WpfTestSvgControl/Resources/PanToolDown.cur new file mode 100644 index 000000000..5f3c236ba Binary files /dev/null and b/Samples/WpfTestSvgControl/Resources/PanToolDown.cur differ diff --git a/Samples/WpfTestSvgControl/SettingsPage.xaml b/Samples/WpfTestSvgControl/SettingsPage.xaml new file mode 100644 index 000000000..0b3e3aa46 --- /dev/null +++ b/Samples/WpfTestSvgControl/SettingsPage.xaml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + Security feature, which is only useful in screenshot to be posted on the web. + + + + + + Indicates whether to search the selected directory recursively. + + + + + + Indicates whether to show the input SVG file tab. + + + + + + Indicates whether to show the output XAML file tab. + + + Default SVG Directory: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/SvgPage.xaml.cs b/Samples/WpfTestSvgControl/SvgPage.xaml.cs new file mode 100644 index 000000000..88c2f6a88 --- /dev/null +++ b/Samples/WpfTestSvgControl/SvgPage.xaml.cs @@ -0,0 +1,411 @@ +using System; +using System.IO; +using System.IO.Packaging; +using System.IO.Compression; + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Xps; +using System.Windows.Xps.Packaging; + +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Utils; +using ICSharpCode.AvalonEdit.Search; +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Folding; +using ICSharpCode.AvalonEdit.Highlighting; + +using Microsoft.Win32; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for SvgPage.xaml + /// + public partial class SvgPage : Page + { + #region Private Fields + + private string _currentFileName; + + private MainWindow _mainWindow; + + private FoldingManager _foldingManager; + private XmlFoldingStrategy _foldingStrategy; + + private readonly SearchPanel _searchPanel; + + #endregion + + #region Constructors and Destructor + + public SvgPage() + { + InitializeComponent(); + + textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML"); + TextEditorOptions options = textEditor.Options; + if (options != null) + { + //options.AllowScrollBelowDocument = true; + options.EnableHyperlinks = true; + options.EnableEmailHyperlinks = true; + options.EnableVirtualSpace = false; + options.HighlightCurrentLine = true; + //options.ShowSpaces = true; + //options.ShowTabs = true; + //options.ShowEndOfLine = true; + } + + textEditor.ShowLineNumbers = true; + + _foldingManager = FoldingManager.Install(textEditor.TextArea); + _foldingStrategy = new XmlFoldingStrategy(); + + textEditor.CommandBindings.Add(new CommandBinding( + ApplicationCommands.Print, OnPrint, OnCanExecuteTextEditorCommand)); + textEditor.CommandBindings.Add(new CommandBinding( + ApplicationCommands.PrintPreview, OnPrintPreview, OnCanExecuteTextEditorCommand)); + + _searchPanel = SearchPanel.Install(textEditor); + } + + #endregion + + #region Public Properties + + public MainWindow MainWindow + { + get { + return _mainWindow; + } + set { + _mainWindow = value; + } + } + + #endregion + + #region Public Methods + + public void LoadDocument(string documentFileName) + { + if (textEditor == null || string.IsNullOrWhiteSpace(documentFileName)) + { + return; + } + + if (!string.IsNullOrWhiteSpace(_currentFileName) && File.Exists(_currentFileName)) + { + // Prevent reloading the same file, just in case we are editing... + if (string.Equals(documentFileName, _currentFileName, StringComparison.OrdinalIgnoreCase)) + { + return; + } + } + + string fileExt = Path.GetExtension(documentFileName); + if (string.Equals(fileExt, ".svgz", StringComparison.OrdinalIgnoreCase)) + { + using (FileStream fileStream = File.OpenRead(documentFileName)) + { + using (GZipStream zipStream = + new GZipStream(fileStream, CompressionMode.Decompress)) + { + // Text Editor does not work with this stream, so we read the data to memory stream... + MemoryStream memoryStream = new MemoryStream(); + // Use this method is used to read all bytes from a stream. + int totalCount = 0; + int bufferSize = 512; + byte[] buffer = new byte[bufferSize]; + while (true) + { + int bytesRead = zipStream.Read(buffer, 0, bufferSize); + if (bytesRead == 0) + { + break; + } + else + { + memoryStream.Write(buffer, 0, bytesRead); + } + totalCount += bytesRead; + } + + if (totalCount > 0) + { + memoryStream.Position = 0; + } + + textEditor.Load(memoryStream); + + memoryStream.Close(); + } + } + } + else + { + textEditor.Load(documentFileName); + } + + if (_foldingManager == null || _foldingStrategy == null) + { + _foldingManager = FoldingManager.Install(textEditor.TextArea); + _foldingStrategy = new XmlFoldingStrategy(); + } + _foldingStrategy.UpdateFoldings(_foldingManager, textEditor.Document); + + _currentFileName = documentFileName; + } + + public void UnloadDocument() + { + if (textEditor != null) + { + textEditor.Document.Text = string.Empty; + } + _currentFileName = null; + } + + public void PageSelected(bool isSelected) + { + if (isSelected) + { + if (textEditor.TextArea.IsKeyboardFocusWithin) + { + Keyboard.Focus(textEditor.TextArea); + } + } + } + + #endregion + + #region Private Methods + + private void OnOpenFileClick(object sender, RoutedEventArgs e) + { + OpenFileDialog dlg = new OpenFileDialog(); + dlg.CheckFileExists = true; + if (dlg.ShowDialog() ?? false) + { + _currentFileName = dlg.FileName; + textEditor.Load(_currentFileName); + textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension( + Path.GetExtension(_currentFileName)); + } + } + + private void OnSaveFileClick(object sender, EventArgs e) + { + if (_currentFileName == null) + { + SaveFileDialog dlg = new SaveFileDialog(); + dlg.Title = "Save As"; + dlg.Filter = "SVG Files|*.svg;*.svgz"; + dlg.DefaultExt = ".svg"; + if (dlg.ShowDialog() ?? false) + { + _currentFileName = dlg.FileName; + } + else + { + return; + } + } + + string fileExt = Path.GetExtension(_currentFileName); + if (string.Equals(fileExt, ".svg", StringComparison.OrdinalIgnoreCase)) + { + textEditor.Save(_currentFileName); + } + else if (string.Equals(fileExt, ".svgz", StringComparison.OrdinalIgnoreCase)) + { + using (FileStream svgzDestFile = File.Create(_currentFileName)) + { + using (GZipStream zipStream = new GZipStream(svgzDestFile, + CompressionMode.Compress, true)) + { + textEditor.Save(zipStream); + } + } + } + } + + private void OnSearchTextClick(object sender, RoutedEventArgs e) + { + if (_searchPanel == null) + { + return; + } + + string searchText = searchTextBox.Text; + + if (!string.IsNullOrWhiteSpace(searchText)) + { + _searchPanel.SearchPattern = searchText; + } + + _searchPanel.Open(); + _searchPanel.Reactivate(); + } + + private void OnSearchTextBoxKeyUp(object sender, KeyEventArgs e) + { + if (e.Key != Key.Enter) + return; + + // your event handler here + e.Handled = true; + + this.OnSearchTextClick(sender, e); + } + + private void OnHighlightingSelectionChanged(object sender, SelectionChangedEventArgs e) + { + } + + #region Printing Methods + + private Block ConvertTextDocumentToBlock() + { + TextDocument document = textEditor.Document; + IHighlighter highlighter = + textEditor.TextArea.GetService(typeof(IHighlighter)) as IHighlighter; + + return DocumentPrinter.ConvertTextDocumentToBlock(document, highlighter); + } + + private FlowDocument CreateFlowDocumentForEditor() + { + FlowDocument doc = new FlowDocument(ConvertTextDocumentToBlock()); + doc.FontFamily = textEditor.FontFamily; + doc.FontSize = textEditor.FontSize; + return doc; + } + + // CanExecuteRoutedEventHandler that only returns true if + // the source is a control. + private void OnCanExecuteTextEditorCommand(object sender, CanExecuteRoutedEventArgs e) + { + var target = e.Source as TextEditor; + + if (target != null) + { + e.CanExecute = true; + } + else + { + e.CanExecute = false; + } + } + + private void OnPrint(object sender, ExecutedRoutedEventArgs e) + { + PrintDialog printDialog = new PrintDialog(); + printDialog.PageRangeSelection = PageRangeSelection.AllPages; + printDialog.UserPageRangeEnabled = true; + bool? dialogResult = printDialog.ShowDialog(); + if (dialogResult != null && dialogResult.Value == false) + { + return; + } + + FlowDocument printSource = this.CreateFlowDocumentForEditor(); + + // Save all the existing settings. + double pageHeight = printSource.PageHeight; + double pageWidth = printSource.PageWidth; + Thickness pagePadding = printSource.PagePadding; + double columnGap = printSource.ColumnGap; + double columnWidth = printSource.ColumnWidth; + + // Make the FlowDocument page match the printed page. + printSource.PageHeight = printDialog.PrintableAreaHeight; + printSource.PageWidth = printDialog.PrintableAreaWidth; + printSource.PagePadding = new Thickness(20); + printSource.ColumnGap = Double.NaN; + printSource.ColumnWidth = printDialog.PrintableAreaWidth; + + Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight); + + DocumentPaginator paginator = ((IDocumentPaginatorSource)printSource).DocumentPaginator; + paginator.PageSize = pageSize; + paginator.ComputePageCount(); + + printDialog.PrintDocument(paginator, "Svg Contents"); + + // Reapply the old settings. + printSource.PageHeight = pageHeight; + printSource.PageWidth = pageWidth; + printSource.PagePadding = pagePadding; + printSource.ColumnGap = columnGap; + printSource.ColumnWidth = columnWidth; + } + + private void OnPrintPreview(object sender, ExecutedRoutedEventArgs e) + { + PrintDialog printDialog = new PrintDialog(); + printDialog.PageRangeSelection = PageRangeSelection.AllPages; + printDialog.UserPageRangeEnabled = true; + bool? dialogResult = printDialog.ShowDialog(); + if (dialogResult != null && dialogResult.Value == false) + { + return; + } + + FlowDocument printSource = this.CreateFlowDocumentForEditor(); + + // Save all the existing settings. + double pageHeight = printSource.PageHeight; + double pageWidth = printSource.PageWidth; + Thickness pagePadding = printSource.PagePadding; + double columnGap = printSource.ColumnGap; + double columnWidth = printSource.ColumnWidth; + + // Make the FlowDocument page match the printed page. + printSource.PageHeight = printDialog.PrintableAreaHeight; + printSource.PageWidth = printDialog.PrintableAreaWidth; + printSource.PagePadding = new Thickness(20); + printSource.ColumnGap = Double.NaN; + printSource.ColumnWidth = printDialog.PrintableAreaWidth; + + Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight); + + MemoryStream xpsStream = new MemoryStream(); + Package package = Package.Open(xpsStream, FileMode.Create, FileAccess.ReadWrite); + string packageUriString = "memorystream://data.xps"; + PackageStore.AddPackage(new Uri(packageUriString), package); + + XpsDocument xpsDocument = new XpsDocument(package, CompressionOption.Normal, packageUriString); + XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument); + + DocumentPaginator paginator = ((IDocumentPaginatorSource)printSource).DocumentPaginator; + paginator.PageSize = pageSize; + paginator.ComputePageCount(); + + writer.Write(paginator); + + // Reapply the old settings. + printSource.PageHeight = pageHeight; + printSource.PageWidth = pageWidth; + printSource.PagePadding = pagePadding; + printSource.ColumnGap = columnGap; + printSource.ColumnWidth = columnWidth; + + PrintPreviewWindow printPreview = new PrintPreviewWindow(); + printPreview.Width = this.ActualWidth; + printPreview.Height = this.ActualHeight; + printPreview.Owner = Application.Current.MainWindow; + + printPreview.LoadDocument(xpsDocument, package, packageUriString); + + printPreview.Show(); + } + + #endregion + + #endregion + } +} diff --git a/Samples/WpfTestSvgControl/TraceDocument.xaml b/Samples/WpfTestSvgControl/TraceDocument.xaml new file mode 100644 index 000000000..a2a9b7614 --- /dev/null +++ b/Samples/WpfTestSvgControl/TraceDocument.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/TraceDocument.xaml.cs b/Samples/WpfTestSvgControl/TraceDocument.xaml.cs new file mode 100644 index 000000000..7c64af364 --- /dev/null +++ b/Samples/WpfTestSvgControl/TraceDocument.xaml.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Controls.Primitives; + +namespace WpfTestSvgControl +{ + public partial class TraceDocument : FlowDocument, ITraceTextSink + { + private delegate void AppendTextDelegate(string msg, string style); + + private TraceListener _listener; + + public TraceDocument() + { + AutoAttach = false; + InitializeComponent(); + } + + public bool AutoAttach { get; set; } + + public void Event(string msg, TraceEventType eventType) + { + Append(msg, eventType.ToString()); + } + + public void Fail(string msg) + { + Append(msg, "Fail"); + } + + public void Startup() + { + if (_listener == null) + { + _listener = new TraceTextSource(this); + Trace.Listeners.Add(_listener); + } + } + + public void Shutdown() + { + if (_listener != null) + { + Trace.Listeners.Remove(_listener); + _listener.Dispose(); + _listener = null; + } + } + + private void Append(string msg, string style) + { + if (Dispatcher.CheckAccess()) + { + Debug.Assert(Blocks.LastBlock != null); + Debug.Assert(Blocks.LastBlock is Paragraph); + var run = new Run(msg); + + run.Style = (Style)(Resources[style]); + if (run.Style == null) + run.Style = (Style)(Resources["Information"]); + + ((Paragraph)Blocks.LastBlock).Inlines.Add(run); + + ScrollParent(this); + } + else + { + Dispatcher.Invoke(new AppendTextDelegate(Append), msg, style); + } + } + + private static void ScrollParent(FrameworkContentElement element) + { + if (element != null) + { + if (element.Parent is TextBoxBase) + ((TextBoxBase)element.Parent).ScrollToEnd(); + + else if (element.Parent is ScrollViewer) + ((ScrollViewer)element.Parent).ScrollToEnd(); + + else + ScrollParent(element.Parent as FrameworkContentElement); + } + } + + private void Document_Loaded(object sender, RoutedEventArgs e) + { + if (AutoAttach && _listener == null) + { + _listener = new TraceTextSource(this); + Trace.Listeners.Add(_listener); + } + } + + private void Document_Unloaded(object sender, RoutedEventArgs e) + { + if (AutoAttach && _listener != null) + { + Trace.Listeners.Remove(_listener); + _listener.Dispose(); + _listener = null; + } + } + } +} diff --git a/Samples/WpfTestSvgControl/TraceTextSource.cs b/Samples/WpfTestSvgControl/TraceTextSource.cs new file mode 100644 index 000000000..c0d21e1a1 --- /dev/null +++ b/Samples/WpfTestSvgControl/TraceTextSource.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics; + +namespace WpfTestSvgControl +{ + interface ITraceTextSink + { + void Fail(string msg); + void Event(string msg, TraceEventType eventType); + } + + class TraceTextSource : TraceListener + { + public ITraceTextSink Sink { get; private set; } + private bool _fail; + private TraceEventType _eventType = TraceEventType.Information; + + public TraceTextSource(ITraceTextSink sink) + { + Debug.Assert(sink != null); + Sink = sink; + } + + public override void Fail(string message) + { + _fail = true; + base.Fail(message); + } + + public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) + { + _eventType = eventType; + base.TraceEvent(eventCache, source, eventType, id, message); + } + + public override void Write(string message) + { + if (IndentLevel > 0) + message = message.PadLeft(IndentLevel + message.Length, '\t'); + + if (_fail) + Sink.Fail(message); + + else + Sink.Event(message, _eventType); + + _fail = false; + _eventType = TraceEventType.Information; + } + + public override void WriteLine(string message) + { + Write(message + "\n"); + } + } +} diff --git a/Samples/WpfTestSvgControl/WpfTestSvgControl.csproj b/Samples/WpfTestSvgControl/WpfTestSvgControl.csproj new file mode 100644 index 000000000..e3e0f9852 --- /dev/null +++ b/Samples/WpfTestSvgControl/WpfTestSvgControl.csproj @@ -0,0 +1,333 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {28586359-004C-45B5-823E-38F714784B31} + WinExe + Properties + WpfTestSvgControl + WpfTestSvgControl + v4.5.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + App.ico + + + + true + full + false + Output\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + false + + + pdbonly + true + Output\ + TRACE + prompt + 4 + AllRules.ruleset + false + + + WpfTestSvgControl.App + + + + + ..\..\Libraries\ICSharpCode.AvalonEdit.dll + + + + ..\..\Libraries\SharpVectors.ShellFileDialogs.dll + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + DrawingHelpWindow.xaml + + + + + + ZoomPanOverview.xaml + + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + App.xaml + Code + + + DebugPage.xaml + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + + + DrawingPage.xaml + + + + + PrintPreviewWindow.xaml + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + SettingsPage.xaml + + + SvgPage.xaml + + + TraceDocument.xaml + + + + XamlPage.xaml + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + {E8056611-E49C-4BC3-A682-A629D5CEC11C} + SharpVectors.Converters.Wpf + + + {D6BB65FC-240E-4241-B2ED-A7FB3F13E978} + SharpVectors.Core + + + {351B0A6E-2F6B-497A-844B-DCB5A502FB0D} + SharpVectors.Css + + + {FE34CBC0-D23C-4A95-BA64-83A031814010} + SharpVectors.Dom + + + {5D336F48-3FB9-4382-B4B9-06974C764007} + SharpVectors.Model + + + {A2576CE0-E492-490F-97E9-C0E7ABAFAF27} + SharpVectors.Rendering.Wpf + + + {2CD52982-A1C2-4A14-9D69-D64719357216} + SharpVectors.Runtime.Wpf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/WpfTestSvgControl/XamlPage.xaml b/Samples/WpfTestSvgControl/XamlPage.xaml new file mode 100644 index 000000000..7a4367348 --- /dev/null +++ b/Samples/WpfTestSvgControl/XamlPage.xaml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Samples/WpfTestSvgControl/XamlPage.xaml.cs b/Samples/WpfTestSvgControl/XamlPage.xaml.cs new file mode 100644 index 000000000..b654a1265 --- /dev/null +++ b/Samples/WpfTestSvgControl/XamlPage.xaml.cs @@ -0,0 +1,419 @@ +using System; +using System.IO; +using System.IO.Packaging; +using System.IO.Compression; + +using System.Windows; +using System.Windows.Input; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Xps; +using System.Windows.Xps.Packaging; + +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Utils; +using ICSharpCode.AvalonEdit.Search; +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Folding; +using ICSharpCode.AvalonEdit.Highlighting; + +using Microsoft.Win32; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for XamlPage.xaml + /// + public partial class XamlPage : Page + { + #region Private Fields + + private string _currentFileName; + + private MainWindow _mainWindow; + + private FoldingManager _foldingManager; + private XmlFoldingStrategy _foldingStrategy; + + private readonly SearchPanel _searchPanel; + + #endregion + + #region Constructors and Destructor + + public XamlPage() + { + InitializeComponent(); + + textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML"); + TextEditorOptions options = textEditor.Options; + if (options != null) + { + //options.AllowScrollBelowDocument = true; + options.EnableHyperlinks = true; + options.EnableEmailHyperlinks = true; + options.EnableVirtualSpace = false; + options.HighlightCurrentLine = true; + //options.ShowSpaces = true; + //options.ShowTabs = true; + //options.ShowEndOfLine = true; + } + textEditor.ShowLineNumbers = true; + + _foldingManager = FoldingManager.Install(textEditor.TextArea); + _foldingStrategy = new XmlFoldingStrategy(); + + textEditor.CommandBindings.Add(new CommandBinding( + ApplicationCommands.Print, OnPrint, OnCanExecuteTextEditorCommand)); + textEditor.CommandBindings.Add(new CommandBinding( + ApplicationCommands.PrintPreview, OnPrintPreview, OnCanExecuteTextEditorCommand)); + + _searchPanel = SearchPanel.Install(textEditor); + } + + #endregion + + #region Public Properties + + public MainWindow MainWindow + { + get { + return _mainWindow; + } + set { + _mainWindow = value; + } + } + + #endregion + + #region Public Methods + + public void LoadDocument(string documentFileName) + { + if (textEditor == null || string.IsNullOrWhiteSpace(documentFileName)) + { + return; + } + + string fileExt = Path.GetExtension(documentFileName); + if (string.Equals(fileExt, ".zaml", StringComparison.OrdinalIgnoreCase)) + { + using (FileStream fileStream = File.OpenRead(documentFileName)) + { + using (GZipStream zipStream = new GZipStream(fileStream, CompressionMode.Decompress)) + { + // Text Editor does not work with this stream, so we read the data to memory stream... + MemoryStream memoryStream = new MemoryStream(); + // Use this method is used to read all bytes from a stream. + int totalCount = 0; + int bufferSize = 512; + byte[] buffer = new byte[bufferSize]; + while (true) + { + int bytesRead = zipStream.Read(buffer, 0, bufferSize); + if (bytesRead == 0) + { + break; + } + else + { + memoryStream.Write(buffer, 0, bytesRead); + } + totalCount += bytesRead; + } + + if (totalCount > 0) + { + memoryStream.Position = 0; + } + + textEditor.Load(memoryStream); + + memoryStream.Close(); + } + } + } + else + { + textEditor.Load(documentFileName); + } + + if (_foldingManager == null || _foldingStrategy == null) + { + _foldingManager = FoldingManager.Install(textEditor.TextArea); + _foldingStrategy = new XmlFoldingStrategy(); + } + + _foldingStrategy.UpdateFoldings(_foldingManager, textEditor.Document); + } + + public void UnloadDocument() + { + if (textEditor != null) + { + textEditor.Document.Text = string.Empty; + } + } + + public void PageSelected(bool isSelected) + { + if (isSelected) + { + if (textEditor.TextArea.IsKeyboardFocusWithin) + { + Keyboard.Focus(textEditor.TextArea); + } + } + } + + #endregion + + #region Private Methods + + private void OnOpenFileClick(object sender, RoutedEventArgs e) + { + OpenFileDialog dlg = new OpenFileDialog(); + dlg.CheckFileExists = true; + if (dlg.ShowDialog() ?? false) + { + _currentFileName = dlg.FileName; + textEditor.Load(_currentFileName); + textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension( + Path.GetExtension(_currentFileName)); + } + } + + private void OnSaveFileClick(object sender, EventArgs e) + { + if (_currentFileName == null) + { + SaveFileDialog dlg = new SaveFileDialog(); + dlg.Title = "Save As (.zaml is GZip compressed file used by SharpVectors)"; + dlg.Filter = "SVG Files|*.xaml;*.zaml"; + dlg.DefaultExt = ".xaml"; + if (dlg.ShowDialog() ?? false) + { + _currentFileName = dlg.FileName; + } + else + { + return; + } + } + + string fileExt = Path.GetExtension(_currentFileName); + if (string.Equals(fileExt, ".xaml", StringComparison.OrdinalIgnoreCase)) + { + textEditor.Save(_currentFileName); + } + else if (string.Equals(fileExt, ".zaml", StringComparison.OrdinalIgnoreCase)) + { + using (FileStream zamlDestFile = File.Create(_currentFileName)) + { + using (GZipStream zipStream = new GZipStream(zamlDestFile, + CompressionMode.Compress, true)) + { + textEditor.Save(zipStream); + } + } + } + } + + private void OnSearchTextClick(object sender, RoutedEventArgs e) + { + if (_searchPanel == null) + { + return; + } + + string searchText = searchTextBox.Text; + + if (!string.IsNullOrWhiteSpace(searchText)) + { + _searchPanel.SearchPattern = searchText; + } + + _searchPanel.Open(); + _searchPanel.Reactivate(); + } + + private void OnSearchTextBoxKeyUp(object sender, KeyEventArgs e) + { + if (e.Key != Key.Enter) + return; + + // your event handler here + e.Handled = true; + + textEditor.Select(textEditor.SelectionStart, 1); + searchTextBox.Focus(); + + this.OnSearchTextClick(sender, e); + } + + private void OnHighlightingSelectionChanged(object sender, SelectionChangedEventArgs e) + { + } + + #endregion + + #region Printing Methods + + private Block ConvertTextDocumentToBlock() + { + TextDocument document = textEditor.Document; + IHighlighter highlighter = + textEditor.TextArea.GetService(typeof(IHighlighter)) as IHighlighter; + + return DocumentPrinter.ConvertTextDocumentToBlock(document, highlighter); + + //Paragraph p = new Paragraph(); + //foreach (DocumentLine line in document.Lines) + //{ + // int lineNumber = line.LineNumber; + // HighlightedInlineBuilder inlineBuilder = new HighlightedInlineBuilder(document.GetText(line)); + // if (highlighter != null) + // { + // HighlightedLine highlightedLine = highlighter.HighlightLine(lineNumber); + // int lineStartOffset = line.Offset; + // foreach (HighlightedSection section in highlightedLine.Sections) + // inlineBuilder.SetHighlighting(section.Offset - lineStartOffset, section.Length, section.Color); + // } + // p.Inlines.AddRange(inlineBuilder.CreateRuns()); + // p.Inlines.Add(new LineBreak()); + //} + + //return p; + } + + private FlowDocument CreateFlowDocumentForEditor() + { + FlowDocument doc = new FlowDocument(ConvertTextDocumentToBlock()); + doc.FontFamily = textEditor.FontFamily; + doc.FontSize = textEditor.FontSize; + return doc; + } + + // CanExecuteRoutedEventHandler that only returns true if + // the source is a control. + private void OnCanExecuteTextEditorCommand(object sender, CanExecuteRoutedEventArgs e) + { + var target = e.Source as TextEditor; + + if (target != null) + { + e.CanExecute = true; + } + else + { + e.CanExecute = false; + } + } + + private void OnPrint(object sender, ExecutedRoutedEventArgs e) + { + PrintDialog printDialog = new PrintDialog(); + printDialog.PageRangeSelection = PageRangeSelection.AllPages; + printDialog.UserPageRangeEnabled = true; + bool? dialogResult = printDialog.ShowDialog(); + if (dialogResult != null && dialogResult.Value == false) + { + return; + } + + FlowDocument printSource = this.CreateFlowDocumentForEditor(); + + // Save all the existing settings. + double pageHeight = printSource.PageHeight; + double pageWidth = printSource.PageWidth; + Thickness pagePadding = printSource.PagePadding; + double columnGap = printSource.ColumnGap; + double columnWidth = printSource.ColumnWidth; + + // Make the FlowDocument page match the printed page. + printSource.PageHeight = printDialog.PrintableAreaHeight; + printSource.PageWidth = printDialog.PrintableAreaWidth; + printSource.PagePadding = new Thickness(20); + printSource.ColumnGap = Double.NaN; + printSource.ColumnWidth = printDialog.PrintableAreaWidth; + + Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight); + + DocumentPaginator paginator = ((IDocumentPaginatorSource)printSource).DocumentPaginator; + paginator.PageSize = pageSize; + paginator.ComputePageCount(); + + printDialog.PrintDocument(paginator, "Svg Contents"); + + // Reapply the old settings. + printSource.PageHeight = pageHeight; + printSource.PageWidth = pageWidth; + printSource.PagePadding = pagePadding; + printSource.ColumnGap = columnGap; + printSource.ColumnWidth = columnWidth; + } + + private void OnPrintPreview(object sender, ExecutedRoutedEventArgs e) + { + PrintDialog printDialog = new PrintDialog(); + printDialog.PageRangeSelection = PageRangeSelection.AllPages; + printDialog.UserPageRangeEnabled = true; + bool? dialogResult = printDialog.ShowDialog(); + if (dialogResult != null && dialogResult.Value == false) + { + return; + } + + FlowDocument printSource = this.CreateFlowDocumentForEditor(); + + // Save all the existing settings. + double pageHeight = printSource.PageHeight; + double pageWidth = printSource.PageWidth; + Thickness pagePadding = printSource.PagePadding; + double columnGap = printSource.ColumnGap; + double columnWidth = printSource.ColumnWidth; + + // Make the FlowDocument page match the printed page. + printSource.PageHeight = printDialog.PrintableAreaHeight; + printSource.PageWidth = printDialog.PrintableAreaWidth; + printSource.PagePadding = new Thickness(20); + printSource.ColumnGap = Double.NaN; + printSource.ColumnWidth = printDialog.PrintableAreaWidth; + + Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight); + + MemoryStream xpsStream = new MemoryStream(); + Package package = Package.Open(xpsStream, FileMode.Create, FileAccess.ReadWrite); + string packageUriString = "memorystream://data.xps"; + PackageStore.AddPackage(new Uri(packageUriString), package); + + XpsDocument xpsDocument = new XpsDocument(package, CompressionOption.Normal, packageUriString); + XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument); + + DocumentPaginator paginator = ((IDocumentPaginatorSource)printSource).DocumentPaginator; + paginator.PageSize = pageSize; + paginator.ComputePageCount(); + + writer.Write(paginator); + + // Reapply the old settings. + printSource.PageHeight = pageHeight; + printSource.PageWidth = pageWidth; + printSource.PagePadding = pagePadding; + printSource.ColumnGap = columnGap; + printSource.ColumnWidth = columnWidth; + + PrintPreviewWindow printPreview = new PrintPreviewWindow(); + printPreview.Width = this.ActualWidth; + printPreview.Height = this.ActualHeight; + printPreview.Owner = Application.Current.MainWindow; + + printPreview.LoadDocument(xpsDocument, package, packageUriString); + + printPreview.Show(); + } + + #endregion + } +} diff --git a/Samples/WpfTestSvgControl/ZoomPanDimensionConverter.cs b/Samples/WpfTestSvgControl/ZoomPanDimensionConverter.cs new file mode 100644 index 000000000..4f4654a32 --- /dev/null +++ b/Samples/WpfTestSvgControl/ZoomPanDimensionConverter.cs @@ -0,0 +1,41 @@ +using System; +using System.Globalization; + +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; + +using SharpVectors.Runtime; + +namespace WpfTestSvgControl +{ + [MarkupExtensionReturnType(typeof(double))] + public sealed class ZoomPanDimensionConverter : MarkupExtension, IMultiValueConverter + { + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + //NOTE: Cannot pass ExtentWidth or ExtentHeight as one of the values because it does not seem to update + var zoomPanControl = values[3] as ZoomPanControl; + if (values[0] == null || zoomPanControl == null) + return DependencyProperty.UnsetValue; + var size = (double)values[0]; + var offset = (double)values[1]; + var zoom = (double)values[2]; + + var isWidth = string.Equals(parameter?.ToString(), "width", StringComparison.OrdinalIgnoreCase); + return Math.Max(isWidth ? Math.Min(zoomPanControl.ExtentWidth / zoom - offset, size) + : Math.Min(zoomPanControl.ExtentHeight / zoom - offset, size), 0); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + +} diff --git a/Samples/WpfTestSvgControl/ZoomPanMouseHandlingMode.cs b/Samples/WpfTestSvgControl/ZoomPanMouseHandlingMode.cs new file mode 100644 index 000000000..98fb32454 --- /dev/null +++ b/Samples/WpfTestSvgControl/ZoomPanMouseHandlingMode.cs @@ -0,0 +1,32 @@ +namespace WpfTestSvgControl +{ + /// + /// Defines the current state of the mouse handling logic. + /// + public enum ZoomPanMouseHandlingMode + { + /// + /// Not in any special mode. + /// + None, + + /// + /// The user is left-mouse-button-dragging to pan the viewport. + /// + Panning, + + /// + /// The user is holding down shift and left-clicking or right-clicking to zoom in or out. + /// + Zooming, + + /// + /// The user is holding down shift and left-mouse-button-dragging to select a region to zoom to. + /// + DragZooming, + + SelectPoint, + + SelectRectangle + } +} diff --git a/Samples/WpfTestSvgControl/ZoomPanOverview.xaml b/Samples/WpfTestSvgControl/ZoomPanOverview.xaml new file mode 100644 index 000000000..817ba4aff --- /dev/null +++ b/Samples/WpfTestSvgControl/ZoomPanOverview.xaml @@ -0,0 +1,9 @@ + + diff --git a/Samples/WpfTestSvgControl/ZoomPanOverview.xaml.cs b/Samples/WpfTestSvgControl/ZoomPanOverview.xaml.cs new file mode 100644 index 000000000..b8f64cb34 --- /dev/null +++ b/Samples/WpfTestSvgControl/ZoomPanOverview.xaml.cs @@ -0,0 +1,339 @@ +using System; +using System.Diagnostics; + +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Controls; + +using SharpVectors.Runtime; + +namespace WpfTestSvgControl +{ + /// + /// Interaction logic for ZoomPanOverview.xaml + /// + public partial class ZoomPanOverview : UserControl + { + #region Private Fields + + /// + /// The control for creating a drag border + /// + private Border _dragBorder; + + /// + /// The control for creating a drag border + /// + private Border _sizingBorder; + + private bool _sizingEvent; + + /// + /// The control for containing a zoom border + /// + private Canvas _viewportCanvas; + + private Viewbox _viewportBox; + + /// + /// Specifies the current state of the mouse handling logic. + /// + private ZoomPanMouseHandlingMode _mouseHandlingMode = ZoomPanMouseHandlingMode.None; + + /// + /// The point that was clicked relative to the content that is contained within the ZoomPanControl. + /// + private Point _origContentMouseDownPoint; + + #endregion + + #region Constructors and Destructor + + public ZoomPanOverview() + { + InitializeComponent(); + + this.HorizontalContentAlignment = HorizontalAlignment.Center; + this.VerticalContentAlignment = VerticalAlignment.Center; + } + + /// + /// Static constructor to define metadata for the control (and link it to the style in Generic.xaml). + /// + static ZoomPanOverview() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ZoomPanOverview), + new FrameworkPropertyMetadata(typeof(ZoomPanOverview))); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _dragBorder = this.Template.FindName("PART_DraggingBorder", this) as Border; + _sizingBorder = this.Template.FindName("PART_SizingBorder", this) as Border; + _viewportCanvas = this.Template.FindName("PART_Content", this) as Canvas; + _viewportBox = this.Template.FindName("PART_Viewbox", this) as Viewbox; + } + + #endregion + + #region Mouse Event Handlers + + /// + /// Event raised on mouse down in the ZoomPanControl. + /// + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + + var drawingPage = this.GetDrawingPage(); + if (drawingPage != null) + { + drawingPage.SaveZoom(); + } + _mouseHandlingMode = ZoomPanMouseHandlingMode.Panning; + _origContentMouseDownPoint = e.GetPosition(_viewportCanvas); + + if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0) + { + // Shift + left- or right-down initiates zooming mode. + _mouseHandlingMode = ZoomPanMouseHandlingMode.DragZooming; + _dragBorder.Visibility = Visibility.Hidden; + _sizingBorder.Visibility = Visibility.Visible; + Canvas.SetLeft(_sizingBorder, _origContentMouseDownPoint.X); + Canvas.SetTop(_sizingBorder, _origContentMouseDownPoint.Y); + _sizingBorder.Width = 0; + _sizingBorder.Height = 0; + } + else + { + // Just a plain old left-down initiates panning mode. + _mouseHandlingMode = ZoomPanMouseHandlingMode.Panning; + } + + if (_mouseHandlingMode != ZoomPanMouseHandlingMode.None) + { + // Capture the mouse so that we eventually receive the mouse up event. + _viewportCanvas.CaptureMouse(); + e.Handled = true; + } + } + + /// + /// Event raised on mouse up in the ZoomPanControl. + /// + protected override void OnMouseUp(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); + + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.DragZooming) + { + var zoomAndPanControl = GetZoomPanControl(); + var curContentPoint = e.GetPosition(_viewportCanvas); + var rect = GetClip(curContentPoint, _origContentMouseDownPoint, new Point(0, 0), + new Point(_viewportCanvas.Width, _viewportCanvas.Height)); + zoomAndPanControl.AnimatedZoomTo(rect); + _dragBorder.Visibility = Visibility.Visible; + _sizingBorder.Visibility = Visibility.Hidden; + } + _mouseHandlingMode = ZoomPanMouseHandlingMode.None; + _viewportCanvas.ReleaseMouseCapture(); + e.Handled = true; + } + + /// + /// Event raised on mouse move in the ZoomPanControl. + /// + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Panning) + { + var curContentPoint = e.GetPosition(_viewportCanvas); + var rectangleDragVector = curContentPoint - _origContentMouseDownPoint; + // + // When in 'dragging rectangles' mode update the position of the rectangle as the user drags it. + // + _origContentMouseDownPoint = Clamp(e.GetPosition(_viewportCanvas)); + Canvas.SetLeft(_dragBorder, Canvas.GetLeft(_dragBorder) + rectangleDragVector.X); + Canvas.SetTop(_dragBorder, Canvas.GetTop(_dragBorder) + rectangleDragVector.Y); + } + else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.DragZooming) + { + var curContentPoint = e.GetPosition(_viewportCanvas); + var rect = GetClip(curContentPoint, _origContentMouseDownPoint, + new Point(0, 0), new Point(_viewportCanvas.Width, _viewportCanvas.Height)); + + PositionBorderOnCanvas(_sizingBorder, rect); + } + + e.Handled = true; + } + + /// + /// Event raised with the double click command + /// + protected override void OnMouseDoubleClick(MouseButtonEventArgs e) + { + base.OnMouseDoubleClick(e); + + if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0) + { + var drawingPage = this.GetDrawingPage(); + if (drawingPage != null) + { + drawingPage.SaveZoom(); + } + var zoomAndPanControl = GetZoomPanControl(); + zoomAndPanControl.AnimatedSnapTo(e.GetPosition(_viewportCanvas)); + } + } + + #endregion + + #region Background--Visual Brush + + /// + /// The X coordinate of the content focus, this is the point that we are focusing on when zooming. + /// + public FrameworkElement Visual + { + get { return (FrameworkElement)GetValue(VisualProperty); } + set { SetValue(VisualProperty, value); } + } + + public static readonly DependencyProperty VisualProperty = DependencyProperty.Register("Visual", + typeof(FrameworkElement), typeof(ZoomPanOverview), new FrameworkPropertyMetadata(null, OnVisualChanged)); + + private static void OnVisualChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var c = (ZoomPanOverview)d; + + c.SetBackground(e.NewValue as FrameworkElement); + } + + private void SetBackground(FrameworkElement frameworkElement) + { + try + { + frameworkElement = frameworkElement ?? (DataContext as ContentControl)?.Content as FrameworkElement; + if (frameworkElement == null) + { + return; + } + var visualBrush = new VisualBrush + { + Visual = frameworkElement, + ViewboxUnits = BrushMappingMode.RelativeToBoundingBox, + ViewportUnits = BrushMappingMode.RelativeToBoundingBox, + AlignmentX = AlignmentX.Center, + AlignmentY = AlignmentY.Center, + TileMode = TileMode.None, + Stretch = Stretch.Uniform + }; + + if (!_sizingEvent) + { + frameworkElement.SizeChanged += (s, e) => + { + _viewportCanvas.Height = frameworkElement.ActualHeight; + _viewportCanvas.Width = frameworkElement.ActualWidth; + _viewportCanvas.Background = visualBrush; + }; + + _viewportCanvas.Height = frameworkElement.ActualHeight; + _viewportCanvas.Width = frameworkElement.ActualWidth; + _viewportCanvas.Background = visualBrush; + + _sizingEvent = true; + } + else + { + _viewportCanvas.Height = frameworkElement.ActualHeight; + _viewportCanvas.Width = frameworkElement.ActualWidth; + _viewportCanvas.Background = visualBrush; + } + } + catch (Exception ex) + { + Trace.TraceError(ex.ToString()); + } + } + + #endregion + + #region Private Methods + + private DrawingPage GetDrawingPage() + { + return this.DataContext as DrawingPage; + } + + private ZoomPanControl GetZoomPanControl() + { + var zoomAndPanControl = (this.DataContext as DrawingPage)?.ZoomPanContent; + if (zoomAndPanControl == null) + throw new NullReferenceException("DataContext is not of type ZoomPanControl"); + return zoomAndPanControl; + } + + /// + /// Moves and sized a border on a Canvas according to a Rect + /// + /// Border to be moved and sized + /// Rect that specifies the size and postion of the Border on the Canvas + private static void PositionBorderOnCanvas(Border border, Rect rect) + { + Canvas.SetLeft(border, rect.Left); + Canvas.SetTop(border, rect.Top); + border.Width = rect.Width; + border.Height = rect.Height; + } + + /// + /// Limits the extent of a Point to the area where X and Y are at least 0 + /// + /// Point to be clamped + /// + private static Point Clamp(Point value) + { + return new Point(Math.Max(value.X, 0), Math.Max(value.Y, 0)); + } + + /// + /// Limits the extent of a Point to the area between two points + /// + /// + /// Point specifiying the Top Left corner + /// Point specifiying the Bottom Right corner + /// The Point clamped by the Top Left and Bottom Right points + private static Point Clamp(Point value, Point topLeft, Point bottomRight) + { + return new Point(Math.Max(Math.Min(value.X, bottomRight.X), topLeft.X), + Math.Max(Math.Min(value.Y, bottomRight.Y), topLeft.Y)); + } + + /// + /// Return a Rect that specificed by two points and clipped by a rectangle specified + /// by two other points + /// + /// First Point specifing the rectangle to be clipped + /// Second Point specifing the rectangle to be clipped + /// Point specifiying the Top Left corner of the clipping rectangle + /// Point specifiying the Bottom Right corner of the clipping rectangle + /// Rectangle specified by two points clipped by the other two points + private static Rect GetClip(Point value1, Point value2, Point topLeft, Point bottomRight) + { + var point1 = Clamp(value1, topLeft, bottomRight); + var point2 = Clamp(value2, topLeft, bottomRight); + var newTopLeft = new Point(Math.Min(point1.X, point2.X), Math.Min(point1.Y, point2.Y)); + var size = new Size(Math.Abs(point1.X - point2.X), Math.Abs(point1.Y - point2.Y)); + return new Rect(newTopLeft, size); + } + + #endregion + } +} diff --git a/Samples/WpfTestSvgControl/ZoomPanScaleConverter.cs b/Samples/WpfTestSvgControl/ZoomPanScaleConverter.cs new file mode 100644 index 000000000..4588b7913 --- /dev/null +++ b/Samples/WpfTestSvgControl/ZoomPanScaleConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; + +namespace WpfTestSvgControl +{ + /// + /// Used in MainWindow.xaml to converts a scale value to a percentage. + /// It is used to display the 50%, 100%, etc that appears underneath the zoom and pan control. + /// + [MarkupExtensionReturnType(typeof(double))] + public sealed class ZoomPanScaleConverter : MarkupExtension, IValueConverter + { + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } + + /// + /// Convert a fraction to a percentage. + /// + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // Round to an integer value whilst converting. + return (double)(int)((double)value * 100.0); + } + + /// + /// Convert a percentage back to a fraction. + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return (double)value / 100.0; + } + } +} diff --git a/Samples/WpfTestSvgControl/app.config b/Samples/WpfTestSvgControl/app.config new file mode 100644 index 000000000..de8289320 --- /dev/null +++ b/Samples/WpfTestSvgControl/app.config @@ -0,0 +1,3 @@ + + + diff --git a/Samples/WpfTestSvgSample/DrawingPage.xaml.cs b/Samples/WpfTestSvgSample/DrawingPage.xaml.cs index 63f260853..ee35c9447 100644 --- a/Samples/WpfTestSvgSample/DrawingPage.xaml.cs +++ b/Samples/WpfTestSvgSample/DrawingPage.xaml.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; +using System.Collections.Generic; using System.Windows; using System.Windows.Input; @@ -104,6 +105,11 @@ public partial class DrawingPage : Page private DispatcherTimer _dispatcherTimer; + private WpfDrawingDocument _drawingDocument; + + private EmbeddedImageSerializerVisitor _embeddedImageVisitor; + private IList _embeddedImages; + #endregion #region Constructors and Destructor @@ -127,11 +133,31 @@ public DrawingPage() _workingDir = new DirectoryInfo(workDir); + _embeddedImages = new List(); + + _embeddedImageVisitor = new EmbeddedImageSerializerVisitor(true); + _wpfSettings.Visitors.ImageVisitor = _embeddedImageVisitor; + + _embeddedImageVisitor.ImageCreated += OnEmbeddedImageCreated; + this.Loaded += OnPageLoaded; this.Unloaded += OnPageUnloaded; this.SizeChanged += OnPageSizeChanged; } + private void OnEmbeddedImageCreated(object sender, EmbeddedImageSerializerArgs args) + { + if (args == null) + { + return; + } + if (_embeddedImages == null) + { + _embeddedImages = new List(); + } + _embeddedImages.Add(args); + } + #endregion #region Public Properties @@ -159,11 +185,6 @@ public string WorkingDrawingDir { _fileReader.SaveXaml = Directory.Exists(_drawingDir); } - - if (_wpfSettings != null) - { - _wpfSettings.Visitors.ImageVisitor = new EmbeddedImageSerializerVisitor(true, _drawingDir); - } } } } @@ -202,8 +223,6 @@ public WpfDrawingSettings ConversionSettings Directory.Exists(_drawingDir)) { _fileReader.SaveXaml = Directory.Exists(_drawingDir); - - _wpfSettings.Visitors.ImageVisitor = new EmbeddedImageSerializerVisitor(true, _drawingDir); } } } @@ -244,12 +263,21 @@ public MainWindow MainWindow /// /// This allows the same property name to be used for direct and indirect access to the ZoomPanelControl control. /// - public ZoomPanControl ZoomPanContent => zoomPanControl; + public ZoomPanControl ZoomPanContent { + get { + return zoomPanControl; + } + } /// /// This allows the same property name to be used for direct and indirect access to the SVG Canvas control. /// - public SvgDrawingCanvas Viewer => svgViewer; + public SvgDrawingCanvas Viewer + { + get { + return svgViewer; + } + } #endregion @@ -271,6 +299,7 @@ public bool LoadDocument(string svgFilePath) this.UnloadDocument(true); _svgFilePath = svgFilePath; + _saveXaml = _optionSettings.ShowOutputFile; string fileExt = Path.GetExtension(svgFilePath); @@ -282,7 +311,12 @@ public bool LoadDocument(string svgFilePath) _fileReader.SaveXaml = _saveXaml; _fileReader.SaveZaml = false; + _embeddedImageVisitor.SaveImages = !_wpfSettings.IncludeRuntime; + _embeddedImageVisitor.SaveDirectory = _drawingDir; + _wpfSettings.Visitors.ImageVisitor = _embeddedImageVisitor; + DrawingGroup drawing = _fileReader.Read(svgFilePath, workingDir); + _drawingDocument = _fileReader.DrawingDocument; if (drawing != null) { svgViewer.UnloadDiagrams(); @@ -324,12 +358,6 @@ public Task LoadDocumentAsync(string svgFilePath) return Task.FromResult(false); } - DirectoryInfo workingDir = _workingDir; - if (_directoryInfo != null) - { - workingDir = _directoryInfo; - } - string fileExt = Path.GetExtension(svgFilePath); if (!(string.Equals(fileExt, SvgConverter.SvgExt, StringComparison.OrdinalIgnoreCase) || @@ -339,6 +367,23 @@ public Task LoadDocumentAsync(string svgFilePath) return Task.FromResult(false); } + _isLoadingDrawing = true; + + this.UnloadDocument(true); + + DirectoryInfo workingDir = _workingDir; + if (_directoryInfo != null) + { + workingDir = _directoryInfo; + } + + _svgFilePath = svgFilePath; + _saveXaml = _optionSettings.ShowOutputFile; + + _embeddedImageVisitor.SaveImages = !_wpfSettings.IncludeRuntime; + _embeddedImageVisitor.SaveDirectory = _drawingDir; + _wpfSettings.Visitors.ImageVisitor = _embeddedImageVisitor; + if (_fileReader == null) { _fileReader = new FileSvgReader(_wpfSettings); @@ -346,23 +391,18 @@ public Task LoadDocumentAsync(string svgFilePath) _fileReader.SaveZaml = false; } - _isLoadingDrawing = true; - - this.UnloadDocument(true); - - _svgFilePath = svgFilePath; - - MemoryStream drawingStream = new MemoryStream(); + var drawingStream = new MemoryStream(); // Get the UI thread's context var context = TaskScheduler.FromCurrentSynchronizationContext(); return Task.Factory.StartNew(() => { - var saveXaml = _fileReader.SaveXaml; - _fileReader.SaveXaml = true; // For threaded, we will save to avoid loading issue later... +// var saveXaml = _fileReader.SaveXaml; +// _fileReader.SaveXaml = true; // For threaded, we will save to avoid loading issue later... DrawingGroup drawing = _fileReader.Read(svgFilePath, workingDir); - _fileReader.SaveXaml = saveXaml; +// _fileReader.SaveXaml = saveXaml; + _drawingDocument = _fileReader.DrawingDocument; if (drawing != null) { XamlWriter.Save(drawing, drawingStream); @@ -397,6 +437,9 @@ public Task LoadDocumentAsync(string svgFilePath) zoomPanControl.AnimatedZoomTo(bounds); CommandManager.InvalidateRequerySuggested(); + + // The drawing changed, update the source... + _fileReader.Drawing = drawing; } _isLoadingDrawing = false; @@ -413,35 +456,71 @@ public Task LoadDocumentAsync(string svgFilePath) public void UnloadDocument(bool displayMessage = false) { - _svgFilePath = null; - - if (svgViewer != null) + try { - svgViewer.UnloadDiagrams(); + _svgFilePath = null; + _drawingDocument = null; - if (displayMessage) + if (svgViewer != null) { - var drawing = this.DrawText("Loading..."); + svgViewer.UnloadDiagrams(); + + if (displayMessage) + { + var drawing = this.DrawText("Loading..."); + + svgViewer.RenderDiagrams(drawing); + + Rect bounds = svgViewer.Bounds; + if (bounds.IsEmpty) + { + bounds = drawing.Bounds; + } - svgViewer.RenderDiagrams(drawing); + zoomPanControl.ZoomTo(bounds); + return; + } + } - Rect bounds = svgViewer.Bounds; - if (bounds.IsEmpty) + var drawRect = this.DrawRect(); + svgViewer.RenderDiagrams(drawRect); + + zoomPanControl.ZoomTo(drawRect.Bounds); + ClearPrevZoomRect(); + ClearNextZoomRect(); + } + finally + { + if (_embeddedImages != null && _embeddedImages.Count != 0) + { + foreach (var embeddedImage in _embeddedImages) { - bounds = drawing.Bounds; + try + { + if (embeddedImage.Image != null) + { + if (embeddedImage.Image.StreamSource != null) + { + embeddedImage.Image.StreamSource.Dispose(); + } + } + + var imagePath = embeddedImage.ImagePath; + if (!string.IsNullOrWhiteSpace(imagePath) && File.Exists(imagePath)) + { + File.Delete(imagePath); + } + } + catch (IOException ex) + { + Trace.TraceError(ex.ToString()); + // Image this, WPF will typically cache and/or lock loaded images + } } - zoomPanControl.ZoomTo(bounds); - return; + _embeddedImages.Clear(); } } - - var drawRect = this.DrawRect(); - svgViewer.RenderDiagrams(drawRect); - - zoomPanControl.ZoomTo(drawRect.Bounds); - ClearPrevZoomRect(); - ClearNextZoomRect(); } public bool SaveDocument(string fileName) @@ -455,7 +534,6 @@ public bool SaveDocument(string fileName) { return false; } - return _fileReader.Save(fileName, true, false); } @@ -645,29 +723,37 @@ private void OnZoomPanMouseDown(object sender, MouseButtonEventArgs e) _origZoomAndPanControlMouseDownPoint = e.GetPosition(zoomPanControl); _origContentMouseDownPoint = e.GetPosition(svgViewer); - if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0 && - (e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Right)) + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.SelectPoint || + _mouseHandlingMode == ZoomPanMouseHandlingMode.SelectRectangle) + { + } + else { - // Shift + left- or right-down initiates zooming mode. - _mouseHandlingMode = ZoomPanMouseHandlingMode.Zooming; + if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0 && + (e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Right)) + { + // Shift + left- or right-down initiates zooming mode. + _mouseHandlingMode = ZoomPanMouseHandlingMode.Zooming; - if (zoomPanControl != null && _canvasCursor != null) + if (zoomPanControl != null && _canvasCursor != null) + { + zoomPanControl.Cursor = _canvasCursor; + } + } + else if (_mouseButtonDown == MouseButton.Left) { - zoomPanControl.Cursor = _canvasCursor; + // Just a plain old left-down initiates panning mode. + _mouseHandlingMode = ZoomPanMouseHandlingMode.Panning; } - } - else if (_mouseButtonDown == MouseButton.Left) - { - // Just a plain old left-down initiates panning mode. - _mouseHandlingMode = ZoomPanMouseHandlingMode.Panning; - } - if (_mouseHandlingMode != ZoomPanMouseHandlingMode.None) - { - // Capture the mouse so that we eventually receive the mouse up event. - zoomPanControl.CaptureMouse(); - e.Handled = true; + if (_mouseHandlingMode != ZoomPanMouseHandlingMode.None) + { + // Capture the mouse so that we eventually receive the mouse up event. + zoomPanControl.CaptureMouse(); + e.Handled = true; + } } + } /// @@ -675,35 +761,42 @@ private void OnZoomPanMouseDown(object sender, MouseButtonEventArgs e) /// private void OnZoomPanMouseUp(object sender, MouseButtonEventArgs e) { - if (_mouseHandlingMode != ZoomPanMouseHandlingMode.None) + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.SelectPoint || + _mouseHandlingMode == ZoomPanMouseHandlingMode.SelectRectangle) { - if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Zooming) + } + else + { + if (_mouseHandlingMode != ZoomPanMouseHandlingMode.None) { - if (_mouseButtonDown == MouseButton.Left) + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Zooming) { - // Shift + left-click zooms in on the content. - ZoomIn(_origContentMouseDownPoint); + if (_mouseButtonDown == MouseButton.Left) + { + // Shift + left-click zooms in on the content. + ZoomIn(_origContentMouseDownPoint); + } + else if (_mouseButtonDown == MouseButton.Right) + { + // Shift + left-click zooms out from the content. + ZoomOut(_origContentMouseDownPoint); + } } - else if (_mouseButtonDown == MouseButton.Right) + else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.DragZooming) { - // Shift + left-click zooms out from the content. - ZoomOut(_origContentMouseDownPoint); + // When drag-zooming has finished we zoom in on the rectangle that was highlighted by the user. + ApplyDragZoomRect(); } + + zoomPanControl.ReleaseMouseCapture(); + _mouseHandlingMode = ZoomPanMouseHandlingMode.None; + e.Handled = true; } - else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.DragZooming) + + if (zoomPanControl != null && _canvasCursor != null) { - // When drag-zooming has finished we zoom in on the rectangle that was highlighted by the user. - ApplyDragZoomRect(); + zoomPanControl.Cursor = _canvasCursor; } - - zoomPanControl.ReleaseMouseCapture(); - _mouseHandlingMode = ZoomPanMouseHandlingMode.None; - e.Handled = true; - } - - if (zoomPanControl != null && _canvasCursor != null) - { - zoomPanControl.Cursor = _canvasCursor; } } @@ -712,67 +805,74 @@ private void OnZoomPanMouseUp(object sender, MouseButtonEventArgs e) /// private void OnZoomPanMouseMove(object sender, MouseEventArgs e) { - if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Panning) + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.SelectPoint || + _mouseHandlingMode == ZoomPanMouseHandlingMode.SelectRectangle) { - if (zoomPanControl != null) - { - zoomPanControl.Cursor = _panToolCursor; - } - - // - // The user is left-dragging the mouse. - // Pan the viewport by the appropriate amount. - // - Point curContentMousePoint = e.GetPosition(svgViewer); - Vector dragOffset = curContentMousePoint - _origContentMouseDownPoint; - - zoomPanControl.ContentOffsetX -= dragOffset.X; - zoomPanControl.ContentOffsetY -= dragOffset.Y; - - e.Handled = true; } - else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Zooming) + else { - if (zoomPanControl != null && _canvasCursor != null) + if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Panning) { - zoomPanControl.Cursor = _canvasCursor; - } + if (zoomPanControl != null) + { + zoomPanControl.Cursor = _panToolCursor; + } - Point curZoomAndPanControlMousePoint = e.GetPosition(zoomPanControl); - Vector dragOffset = curZoomAndPanControlMousePoint - _origZoomAndPanControlMouseDownPoint; - double dragThreshold = 10; - if (_mouseButtonDown == MouseButton.Left && - (Math.Abs(dragOffset.X) > dragThreshold || - Math.Abs(dragOffset.Y) > dragThreshold)) - { // - // When Shift + left-down zooming mode and the user drags beyond the drag threshold, - // initiate drag zooming mode where the user can drag out a rectangle to select the area - // to zoom in on. + // The user is left-dragging the mouse. + // Pan the viewport by the appropriate amount. // - _mouseHandlingMode = ZoomPanMouseHandlingMode.DragZooming; - Point curContentMousePoint = e.GetPosition(svgViewer); - InitDragZoomRect(_origContentMouseDownPoint, curContentMousePoint); - } + Vector dragOffset = curContentMousePoint - _origContentMouseDownPoint; - e.Handled = true; - } - else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.DragZooming) - { - if (zoomPanControl != null && _canvasCursor != null) + zoomPanControl.ContentOffsetX -= dragOffset.X; + zoomPanControl.ContentOffsetY -= dragOffset.Y; + + e.Handled = true; + } + else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.Zooming) { - zoomPanControl.Cursor = _canvasCursor; + if (zoomPanControl != null && _canvasCursor != null) + { + zoomPanControl.Cursor = _canvasCursor; + } + + Point curZoomAndPanControlMousePoint = e.GetPosition(zoomPanControl); + Vector dragOffset = curZoomAndPanControlMousePoint - _origZoomAndPanControlMouseDownPoint; + double dragThreshold = 10; + if (_mouseButtonDown == MouseButton.Left && + (Math.Abs(dragOffset.X) > dragThreshold || + Math.Abs(dragOffset.Y) > dragThreshold)) + { + // + // When Shift + left-down zooming mode and the user drags beyond the drag threshold, + // initiate drag zooming mode where the user can drag out a rectangle to select the area + // to zoom in on. + // + _mouseHandlingMode = ZoomPanMouseHandlingMode.DragZooming; + + Point curContentMousePoint = e.GetPosition(svgViewer); + InitDragZoomRect(_origContentMouseDownPoint, curContentMousePoint); + } + + e.Handled = true; } + else if (_mouseHandlingMode == ZoomPanMouseHandlingMode.DragZooming) + { + if (zoomPanControl != null && _canvasCursor != null) + { + zoomPanControl.Cursor = _canvasCursor; + } - // - // When in drag zooming mode continously update the position of the rectangle - // that the user is dragging out. - // - Point curContentMousePoint = e.GetPosition(svgViewer); - SetDragZoomRect(_origContentMouseDownPoint, curContentMousePoint); + // + // When in drag zooming mode continously update the position of the rectangle + // that the user is dragging out. + // + Point curContentMousePoint = e.GetPosition(svgViewer); + SetDragZoomRect(_origContentMouseDownPoint, curContentMousePoint); - e.Handled = true; + e.Handled = true; + } } } diff --git a/Samples/WpfTestSvgSample/MainWindow.xaml.cs b/Samples/WpfTestSvgSample/MainWindow.xaml.cs index 1d3b5589a..434982334 100644 --- a/Samples/WpfTestSvgSample/MainWindow.xaml.cs +++ b/Samples/WpfTestSvgSample/MainWindow.xaml.cs @@ -1076,9 +1076,20 @@ private void CloseFile() string[] imageFiles = Directory.GetFiles(_drawingDir, "*.png"); if (imageFiles != null && imageFiles.Length != 0) { - foreach (var imageFile in imageFiles) + try { - File.Delete(imageFile); + foreach (var imageFile in imageFiles) + { + if (File.Exists(imageFile)) + { + File.Delete(imageFile); + } + } + } + catch (IOException ex) + { + Trace.TraceError(ex.ToString()); + // Image this, WPF will typically cache and/or lock loaded images } } } diff --git a/Samples/WpfTestSvgSample/ZoomPanMouseHandlingMode.cs b/Samples/WpfTestSvgSample/ZoomPanMouseHandlingMode.cs index adcf850bf..066c5d0fc 100644 --- a/Samples/WpfTestSvgSample/ZoomPanMouseHandlingMode.cs +++ b/Samples/WpfTestSvgSample/ZoomPanMouseHandlingMode.cs @@ -24,5 +24,9 @@ public enum ZoomPanMouseHandlingMode /// The user is holding down shift and left-mouse-button-dragging to select a region to zoom to. /// DragZooming, + + SelectPoint, + + SelectRectangle } } diff --git a/SharpVectorsAll.sln b/SharpVectorsAll.sln index d4e7b87d8..75706db8c 100644 --- a/SharpVectorsAll.sln +++ b/SharpVectorsAll.sln @@ -38,6 +38,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GdiW3cSvgTestSuite", "Sampl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GdiSvgTestBox", "Samples\GdiSvgTestBox\GdiSvgTestBox.csproj", "{5A71211F-9EDA-4C43-8DDC-E3EB54843113}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfTestSvgControl", "Samples\WpfTestSvgControl\WpfTestSvgControl.csproj", "{28586359-004C-45B5-823E-38F714784B31}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -141,6 +143,12 @@ Global {5A71211F-9EDA-4C43-8DDC-E3EB54843113}.Documentation|Any CPU.Build.0 = Debug|Any CPU {5A71211F-9EDA-4C43-8DDC-E3EB54843113}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A71211F-9EDA-4C43-8DDC-E3EB54843113}.Release|Any CPU.Build.0 = Release|Any CPU + {28586359-004C-45B5-823E-38F714784B31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28586359-004C-45B5-823E-38F714784B31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28586359-004C-45B5-823E-38F714784B31}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU + {28586359-004C-45B5-823E-38F714784B31}.Documentation|Any CPU.Build.0 = Debug|Any CPU + {28586359-004C-45B5-823E-38F714784B31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28586359-004C-45B5-823E-38F714784B31}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -154,6 +162,7 @@ Global {D3B055DA-D97B-4B97-BFB6-18837FFA7C2D} = {A1967865-8F3B-4976-A7EC-8F91EAE4C964} {406733B7-B9C5-4100-8D10-F3CCE9C58B2C} = {A1967865-8F3B-4976-A7EC-8F91EAE4C964} {5A71211F-9EDA-4C43-8DDC-E3EB54843113} = {A1967865-8F3B-4976-A7EC-8F91EAE4C964} + {28586359-004C-45B5-823E-38F714784B31} = {A1967865-8F3B-4976-A7EC-8F91EAE4C964} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECBBFB65-D6D1-4727-BE82-54D52CE8FCF3} diff --git a/Source/SharpVectorConvertersWpf/EmbeddedImageSerializerVisitor.cs b/Source/SharpVectorConvertersWpf/EmbeddedImageSerializerVisitor.cs index 13d47afae..7a91cc780 100644 --- a/Source/SharpVectorConvertersWpf/EmbeddedImageSerializerVisitor.cs +++ b/Source/SharpVectorConvertersWpf/EmbeddedImageSerializerVisitor.cs @@ -14,22 +14,45 @@ namespace SharpVectors.Converters { + public sealed class EmbeddedImageSerializerArgs : EventArgs + { + public EmbeddedImageSerializerArgs(string imagePath, BitmapImage image) + { + this.ImagePath = imagePath; + this.Image = image; + } + + public string ImagePath { get; private set; } + public BitmapImage Image { get; private set; } + } + public sealed class EmbeddedImageSerializerVisitor : WpfEmbeddedImageVisitor { #region Public Private Fields public const string ImageExt = ".png"; + private int _imageCount; + private bool _saveImages; + private bool _converterFallback; private string _namePrefix; private string _saveDirectory; private IDictionary _imageCache; + private event EventHandler _imageCreated; + #endregion #region Constructors and Destructor + public EmbeddedImageSerializerVisitor(bool converterFallback) + : this(false, null) + { + _converterFallback = converterFallback; + } + public EmbeddedImageSerializerVisitor(string saveDirectory) : this(true, saveDirectory) { @@ -55,11 +78,22 @@ public EmbeddedImageSerializerVisitor(bool saveImages, string saveDirectory) Trace.TraceError(ex.ToString()); } + _imageCount = 1; _imageCache = new Dictionary(StringComparer.Ordinal); } #endregion + #region Public Events + + public event EventHandler ImageCreated + { + add { _imageCreated += value; } + remove { _imageCreated -= value; } + } + + #endregion + #region Public Properties public bool SaveImages @@ -67,13 +101,30 @@ public bool SaveImages get { return _saveImages; } + set { + _saveImages = value; + } + } + + public bool ConverterFallback + { + get { + return _converterFallback; + } + set { + _converterFallback = value; + } } + public string SaveDirectory { get { return _saveDirectory; } + set { + _saveDirectory = value; + } } public string ImageNamePrefix @@ -123,7 +174,35 @@ public override ImageSource Visit(SvgImageElement element, WpfDrawingContext con { if (!string.IsNullOrWhiteSpace(imageId) && _imageCache.ContainsKey(imageId)) { - return _imageCache[imageId]; + var cachedSource = _imageCache[imageId]; + if (cachedSource != null) + { + var cachedImage = cachedSource as BitmapImage; + if (cachedImage != null) + { + var imageUri = cachedImage.UriSource; + if (imageUri != null) + { + if (imageUri.IsFile && File.Exists(imageUri.LocalPath)) + { + return cachedImage; + } + _imageCache.Remove(imageId); + } + else if (cachedImage.StreamSource != null) + { + return cachedImage; + } + } + else + { + return cachedSource; + } + } + else + { + _imageCache.Remove(imageId); + } } } @@ -183,7 +262,9 @@ private ImageSource GetImage(SvgImageElement element, WpfDrawingContext context) using (GZipStream zipStream = new GZipStream(stream, CompressionMode.Decompress)) { using (var reader = new FileSvgReader(context.Settings)) + { return new DrawingImage(reader.Read(zipStream)); + } } } } @@ -192,23 +273,27 @@ private ImageSource GetImage(SvgImageElement element, WpfDrawingContext context) using (var stream = new MemoryStream(imageBytes)) { using (var reader = new FileSvgReader(context.Settings)) + { return new DrawingImage(reader.Read(stream)); + } } } } + var memStream = new MemoryStream(imageBytes); + BitmapImage imageSource = new BitmapImage(); imageSource.BeginInit(); + imageSource.StreamSource = memStream; + imageSource.CacheOption = BitmapCacheOption.OnLoad; imageSource.CreateOptions = BitmapCreateOptions.PreservePixelFormat; - imageSource.StreamSource = new MemoryStream(imageBytes); imageSource.EndInit(); - imageSource.Freeze(); + string imagePath = null; - if (isSavingImages && !string.IsNullOrWhiteSpace(imagesDir) - && Directory.Exists(imagesDir)) + if (isSavingImages && !string.IsNullOrWhiteSpace(imagesDir) && Directory.Exists(imagesDir)) { - var imagePath = this.GetImagePath(imagesDir); + imagePath = this.GetImagePath(imagesDir); BitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(imageSource)); @@ -218,19 +303,47 @@ private ImageSource GetImage(SvgImageElement element, WpfDrawingContext context) encoder.Save(fileStream); } - BitmapImage savedSource = new BitmapImage(); + imageSource.CreateOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreImageCache; + imageSource.UriSource = new Uri(imagePath); + + //imageSource.StreamSource.Dispose(); + //imageSource = null; - savedSource.BeginInit(); - savedSource.CacheOption = BitmapCacheOption.OnLoad; - savedSource.CreateOptions = BitmapCreateOptions.PreservePixelFormat; - savedSource.UriSource = new Uri(imagePath); - savedSource.EndInit(); + //BitmapImage savedSource = new BitmapImage(); - savedSource.Freeze(); + //savedSource.BeginInit(); + //savedSource.CacheOption = BitmapCacheOption.None; + //savedSource.CreateOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreImageCache; + //savedSource.UriSource = new Uri(imagePath); + //savedSource.EndInit(); - return savedSource; + //savedSource.Freeze(); + + //if (_imageCreated != null) + //{ + // var eventArgs = new EmbeddedImageSerializerArgs(imagePath, savedSource); + // _imageCreated.Invoke(this, eventArgs); + //} + + //return savedSource; + } + else if (_converterFallback) + { + //if (_imageCreated != null) + //{ + // var eventArgs = new EmbeddedImageSerializerArgs(imagePath, imageSource); + // _imageCreated.Invoke(this, eventArgs); + //} + return new EmbeddedBitmapSource(memStream, imageSource); + } + if (_imageCreated != null) + { + var eventArgs = new EmbeddedImageSerializerArgs(imagePath, imageSource); + _imageCreated.Invoke(this, eventArgs); } + imageSource.Freeze(); + return imageSource; } @@ -241,7 +354,7 @@ private string GetImagePath(string savedDir) savedDir = _saveDirectory; } - int imageCount = 1; + int imageCount = _imageCount; var nextPath = Path.Combine(savedDir, string.Format("{0}{1}{2}", _namePrefix, imageCount, ImageExt)); @@ -252,6 +365,8 @@ private string GetImagePath(string savedDir) _namePrefix, imageCount, ImageExt)); } + _imageCount = imageCount + 1; + return nextPath; } diff --git a/Source/SharpVectorConvertersWpf/FileSvgReader.cs b/Source/SharpVectorConvertersWpf/FileSvgReader.cs index 5b6545d45..792ba952d 100644 --- a/Source/SharpVectorConvertersWpf/FileSvgReader.cs +++ b/Source/SharpVectorConvertersWpf/FileSvgReader.cs @@ -187,7 +187,7 @@ public string ZamlFile } /// - /// Gets the last created drawing. + /// Gets or sets the last created drawing. /// /// /// A specifying the last converted drawing. @@ -197,6 +197,13 @@ public DrawingGroup Drawing get { return _drawing; } + set { + _drawing = value; + if (_drawingDocument != null) + { + _drawingDocument.EnumerateDrawing(_drawing); + } + } } public WpfDrawingDocument DrawingDocument diff --git a/Source/SharpVectorConvertersWpf/SvgConverter.cs b/Source/SharpVectorConvertersWpf/SvgConverter.cs index 1dc91b487..45471eaf7 100644 --- a/Source/SharpVectorConvertersWpf/SvgConverter.cs +++ b/Source/SharpVectorConvertersWpf/SvgConverter.cs @@ -287,6 +287,16 @@ protected virtual void EndProcessing() { _wpfRenderer.EndRender(); } + + if (_wpfSettings != null) + { + var visitors = _wpfSettings.Visitors; + if (visitors != null) + { + visitors.Uninitialize(); + } + } + //TODO: Currently, experimental GC.Collect(); } diff --git a/Source/SharpVectorRenderingGdi/SvgAlertArgs.cs b/Source/SharpVectorRenderingGdi/SvgAlertArgs.cs index 78e418d7d..684d7c3f1 100644 --- a/Source/SharpVectorRenderingGdi/SvgAlertArgs.cs +++ b/Source/SharpVectorRenderingGdi/SvgAlertArgs.cs @@ -9,7 +9,7 @@ public SvgAlertArgs(string message) this.Message = message; } - public string Message { get; } + public string Message { get; private set; } public bool Handled { get; set; } } } diff --git a/Source/SharpVectorRenderingGdi/SvgErrorArgs.cs b/Source/SharpVectorRenderingGdi/SvgErrorArgs.cs index 44270fc9c..e547bb639 100644 --- a/Source/SharpVectorRenderingGdi/SvgErrorArgs.cs +++ b/Source/SharpVectorRenderingGdi/SvgErrorArgs.cs @@ -17,8 +17,8 @@ public SvgErrorArgs(string message, Exception exception) public bool Handled { get; set; } - public string Message { get; } + public string Message { get; private set; } - public Exception Exception { get; } + public Exception Exception { get; private set; } } } diff --git a/Source/SharpVectorRenderingWpf/SharpVectors.Rendering.Wpf.csproj b/Source/SharpVectorRenderingWpf/SharpVectors.Rendering.Wpf.csproj index c669a04c0..845171133 100644 --- a/Source/SharpVectorRenderingWpf/SharpVectors.Rendering.Wpf.csproj +++ b/Source/SharpVectorRenderingWpf/SharpVectors.Rendering.Wpf.csproj @@ -105,6 +105,7 @@ + diff --git a/Source/SharpVectorRenderingWpf/Wpf/WpfDrawingContext.cs b/Source/SharpVectorRenderingWpf/Wpf/WpfDrawingContext.cs index 4e86bc095..1c1e11ce0 100644 --- a/Source/SharpVectorRenderingWpf/Wpf/WpfDrawingContext.cs +++ b/Source/SharpVectorRenderingWpf/Wpf/WpfDrawingContext.cs @@ -5,6 +5,8 @@ using System.Windows; using System.Windows.Media; +using SharpVectors.Runtime; + namespace SharpVectors.Renderers.Wpf { public sealed class WpfDrawingContext : DependencyObject, IEnumerable @@ -589,6 +591,17 @@ public void RegisterDrawing(string elementId, string uniqueId, Drawing drawing) { if (_drawingDocument != null) { + if (_settings != null && _settings.IncludeRuntime) + { + if (!string.IsNullOrWhiteSpace(elementId)) + { + SvgObject.SetId(drawing, elementId); + } + if (!string.IsNullOrWhiteSpace(uniqueId)) + { + SvgObject.SetUniqueId(drawing, uniqueId); + } + } _drawingDocument.Add(elementId, uniqueId, drawing); } } diff --git a/Source/SharpVectorRenderingWpf/Wpf/WpfDrawingDocument.cs b/Source/SharpVectorRenderingWpf/Wpf/WpfDrawingDocument.cs index f82a0aff7..8f7452ef9 100644 --- a/Source/SharpVectorRenderingWpf/Wpf/WpfDrawingDocument.cs +++ b/Source/SharpVectorRenderingWpf/Wpf/WpfDrawingDocument.cs @@ -1,7 +1,6 @@ using System; +using System.Threading.Tasks; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Windows; using System.Windows.Media; @@ -13,6 +12,8 @@ namespace SharpVectors.Renderers.Wpf { public sealed class WpfDrawingDocument : DependencyObject { + #region Private Fields + private Transform _displayTransform; private bool _isEnumerated; @@ -21,6 +22,10 @@ public sealed class WpfDrawingDocument : DependencyObject private IDictionary _idMap; private IDictionary _guidMap; + #endregion + + #region Constructors and Destructor + public WpfDrawingDocument() { _idMap = new Dictionary(StringComparer.Ordinal); @@ -28,6 +33,10 @@ public WpfDrawingDocument() _displayTransform = Transform.Identity; } + #endregion + + #region Public Properties + public Transform DisplayTransform { get { @@ -111,6 +120,10 @@ public ICollection ElementUniqueNames } } + #endregion + + #region Public Methods + public void Initialize(SvgDocument svgDocument, DrawingGroup svgDrawing) { _svgDocument = svgDocument; @@ -191,7 +204,7 @@ public Drawing GetById(string elementId) var svgElement = _svgDocument.GetSvgById(elementId); if (svgElement != null) { - this.EnumerateDrawing(); + // this.EnumerateDrawing(); } if (_idMap.Count != 0 && _idMap.ContainsKey(elementId)) { @@ -254,11 +267,71 @@ public SvgElement GetSvgByUniqueId(string uniqueId) return _svgDocument.GetSvgByUniqueId(uniqueId); } + public void EnumerateDrawing(DrawingGroup drawing) + { + if (drawing == null) + { + return; + } + + _svgDrawing = drawing; + + //TODO: Trying a background run... + Task.Factory.StartNew(() => + { + this.EnumerateDrawing(); + }); + } + + public WpfHitTestResult HitTest(Point point) + { + var svgDrawing = this.PerformHitTest(point); + if (svgDrawing == null) + { + return WpfHitTestResult.Empty; + } + string uniqueId = SvgObject.GetUniqueId(svgDrawing); + if (string.IsNullOrWhiteSpace(uniqueId)) + { + return WpfHitTestResult.Empty; + } + var svgElement = this.GetSvgByUniqueId(uniqueId); + if (svgElement == null) + { + return WpfHitTestResult.Empty; + } + + return new WpfHitTestResult(point, svgElement, svgDrawing); + } + + public WpfHitTestResult HitTest(Rect rect, IntersectionDetail detail) + { + var svgDrawing = this.PerformHitTest(rect, detail); + if (svgDrawing == null) + { + return WpfHitTestResult.Empty; + } + string uniqueId = SvgObject.GetUniqueId(svgDrawing); + if (string.IsNullOrWhiteSpace(uniqueId)) + { + return WpfHitTestResult.Empty; + } + var svgElement = this.GetSvgByUniqueId(uniqueId); + if (svgElement == null) + { + return WpfHitTestResult.Empty; + } + + return new WpfHitTestResult(rect, svgElement, svgDrawing); + } + + #endregion + #region Private Methods #region HitTest Point - private Drawing HitTest(Point pt) + private Drawing PerformHitTest(Point pt) { if (_svgDrawing == null) { @@ -292,7 +365,7 @@ private Drawing HitTest(Point pt) foundDrawing = drawing; break; } - else if (SvgObject.GetType(groupDrawing) == SvgObjectType.Text && + if (SvgObject.GetType(groupDrawing) == SvgObjectType.Text && groupDrawing.Bounds.Contains(ptDisplay)) { foundDrawing = drawing; @@ -448,7 +521,7 @@ private bool HitTestDrawing(DrawingGroup group, Point pt) #region HitTest Geometry - private Drawing HitTest(Rect rect, IntersectionDetail detail) + private Drawing PerformHitTest(Rect rect, IntersectionDetail detail) { if (_svgDrawing == null) { @@ -657,9 +730,52 @@ private void EnumerateDrawing() { return; } + _idMap = new Dictionary(StringComparer.Ordinal); + _guidMap = new Dictionary(StringComparer.Ordinal); + + this.EnumDrawingGroup(_svgDrawing); + _isEnumerated = true; } + // Enumerate the drawings in the DrawingGroup. + private void EnumDrawingGroup(DrawingGroup drawingGroup) + { + DrawingCollection dc = drawingGroup.Children; + + DrawingGroup groupDrawing = null; + + // Enumerate the drawings in the DrawingCollection. + foreach (Drawing drawing in dc) + { + string objectId = SvgObject.GetId(drawing); + if (!string.IsNullOrWhiteSpace(objectId)) + { + _idMap.Add(objectId, drawing); + } + string objectName = (string)drawing.GetValue(FrameworkElement.NameProperty); + if (!string.IsNullOrWhiteSpace(objectName)) + { + _idMap[objectName] = drawing; + } + string uniqueId = SvgObject.GetUniqueId(drawing); + if (!string.IsNullOrWhiteSpace(uniqueId)) + { + _guidMap.Add(uniqueId, drawing); + } + + // If the drawing is a DrawingGroup, call the function recursively. + if (TryCast.Cast(drawing, out groupDrawing)) + { + SvgObjectType objectType = SvgObject.GetType(groupDrawing); + if (objectType != SvgObjectType.Text) + { + EnumDrawingGroup(groupDrawing); + } + } + } + } + #endregion } } diff --git a/Source/SharpVectorRenderingWpf/Wpf/WpfHitTestResult.cs b/Source/SharpVectorRenderingWpf/Wpf/WpfHitTestResult.cs new file mode 100644 index 000000000..6c0470c3e --- /dev/null +++ b/Source/SharpVectorRenderingWpf/Wpf/WpfHitTestResult.cs @@ -0,0 +1,83 @@ +using System; + +using System.Windows; +using System.Windows.Media; + +using SharpVectors.Dom.Svg; + +namespace SharpVectors.Renderers.Wpf +{ + public class WpfHitTestResult + { + public static readonly WpfHitTestResult Empty = new WpfHitTestResult(); + + private Point? _point; + private Rect? _rect; + private Drawing _drawing; + private SvgElement _element; + + private WpfHitTestResult() + { + } + + public WpfHitTestResult(int pointX, int pointY, SvgElement element, Drawing drawing) + : this(new Point(pointX, pointY), element, drawing) + { + } + + public WpfHitTestResult(Point point, SvgElement element, Drawing drawing) + { + _point = point; + _element = element; + _drawing = drawing; + } + + public WpfHitTestResult(Rect rect, SvgElement element, Drawing drawing) + { + _rect = rect; + _element = element; + _drawing = drawing; + } + + public bool IsHit + { + get { + return (_element != null && _drawing != null); + } + } + + /// + /// The point value to hit test against. + /// + public Point? Point + { + get { + return _point; + } + } + + public Rect? Rect + { + get { + return _rect; + } + } + + /// + /// Gets the SVG object that was hit. + /// + public SvgElement Element + { + get { + return _element; + } + } + + public Drawing Drawing + { + get { + return _drawing; + } + } + } +} diff --git a/Source/SharpVectorRenderingWpf/Wpf/WpfImageRendering.cs b/Source/SharpVectorRenderingWpf/Wpf/WpfImageRendering.cs index 8486ac89f..625d68744 100644 --- a/Source/SharpVectorRenderingWpf/Wpf/WpfImageRendering.cs +++ b/Source/SharpVectorRenderingWpf/Wpf/WpfImageRendering.cs @@ -346,12 +346,12 @@ public override void Render(WpfDrawingRenderer renderer) if (!_idAssigned && !string.IsNullOrWhiteSpace(elementId) && !context.IsRegisteredId(elementId)) { - SvgObject.SetName(drawGroup, elementId); - context.RegisterId(elementId); if (context.IncludeRuntime) { + SvgObject.SetName(drawGroup, elementId); + SvgObject.SetId(drawGroup, elementId); } } @@ -363,12 +363,12 @@ public override void Render(WpfDrawingRenderer renderer) { if (!_idAssigned && !string.IsNullOrWhiteSpace(elementId) && !context.IsRegisteredId(elementId)) { - SvgObject.SetName(imageSource, elementId); - context.RegisterId(elementId); if (context.IncludeRuntime) { + SvgObject.SetName(imageSource, elementId); + SvgObject.SetId(imageSource, elementId); } } @@ -501,7 +501,7 @@ private ImageSource GetBitmap(SvgImageElement element, WpfDrawingContext context imageSource.CacheOption = BitmapCacheOption.OnLoad; imageSource.CreateOptions = BitmapCreateOptions.IgnoreImageCache | BitmapCreateOptions.PreservePixelFormat; - imageSource.UriSource = imageUri; + imageSource.UriSource = imageUri; imageSource.EndInit(); // imageSource.Freeze(); diff --git a/Source/SharpVectorRenderingWpf/Wpf/WpfRenderingBase.cs b/Source/SharpVectorRenderingWpf/Wpf/WpfRenderingBase.cs index e55946240..9b31c9b29 100644 --- a/Source/SharpVectorRenderingWpf/Wpf/WpfRenderingBase.cs +++ b/Source/SharpVectorRenderingWpf/Wpf/WpfRenderingBase.cs @@ -165,9 +165,11 @@ protected void Rendered(Drawing drawing) { if (drawing != null && _context != null) { - if (string.IsNullOrWhiteSpace(_elementId)) + if (string.IsNullOrWhiteSpace(_elementId) + || !string.Equals(_elementId, _svgElement.Id)) { - _elementId = this.GetElementName(); + //_elementId = this.GetElementName(); + _elementId = _svgElement.Id; } if (string.IsNullOrWhiteSpace(_uniqueId)) { diff --git a/Source/SharpVectorRenderingWpf/Wpf/WpfSvgRendering.cs b/Source/SharpVectorRenderingWpf/Wpf/WpfSvgRendering.cs index 6f97dd92d..257bd0dd0 100644 --- a/Source/SharpVectorRenderingWpf/Wpf/WpfSvgRendering.cs +++ b/Source/SharpVectorRenderingWpf/Wpf/WpfSvgRendering.cs @@ -156,8 +156,8 @@ public override void BeforeRender(WpfDrawingRenderer renderer) // We have already applied the transform, which will translate to the start point... if (transform is TranslateTransform) { - _drawGroup.ClipGeometry = new RectangleGeometry( - new Rect(0, 0, elmRect.Width, elmRect.Height)); + //_drawGroup.ClipGeometry = new RectangleGeometry( + // new Rect(0, 0, elmRect.Width, elmRect.Height)); } else { @@ -198,7 +198,11 @@ public override void Render(WpfDrawingRenderer renderer) } // Register this drawing with the Drawing-Document... - this.Rendered(_drawGroup); + // ...but not the root SVG object, since there is not point for that + if (!_isRoot) + { + this.Rendered(_drawGroup); + } } public override void AfterRender(WpfDrawingRenderer renderer) diff --git a/Source/SharpVectorRuntimeWpf/EmbeddedBitmapSource.cs b/Source/SharpVectorRuntimeWpf/EmbeddedBitmapSource.cs index ed4b37175..1a84981e8 100644 --- a/Source/SharpVectorRuntimeWpf/EmbeddedBitmapSource.cs +++ b/Source/SharpVectorRuntimeWpf/EmbeddedBitmapSource.cs @@ -54,6 +54,33 @@ public EmbeddedBitmapSource(MemoryStream stream) this.EndInit(); } + public EmbeddedBitmapSource(MemoryStream stream, BitmapImage image) + : this() + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + _stream = stream; + // Associated this class with source. + this.BeginInit(); + + if (image == null) + { + _bitmap = new BitmapImage(); + + _bitmap.BeginInit(); + _bitmap.StreamSource = _stream; + _bitmap.EndInit(); + } + else + { + _bitmap = image; + } + + this.InitWicInfo(_bitmap); + this.EndInit(); + } + #endregion Constructors #region Public Properties diff --git a/Source/SharpVectorRuntimeWpf/SvgDrawingCanvas.cs b/Source/SharpVectorRuntimeWpf/SvgDrawingCanvas.cs index 250c90672..c307805c8 100644 --- a/Source/SharpVectorRuntimeWpf/SvgDrawingCanvas.cs +++ b/Source/SharpVectorRuntimeWpf/SvgDrawingCanvas.cs @@ -25,7 +25,7 @@ public class SvgDrawingCanvas : Canvas { #region Private Fields - private static readonly Action EmptyDelegate = delegate { }; + //private static readonly Action EmptyDelegate = delegate { }; private bool _drawForInteractivity; diff --git a/Source/SharpVectorRuntimeWpf/SvgObject.cs b/Source/SharpVectorRuntimeWpf/SvgObject.cs index 165ed9927..df410a8da 100644 --- a/Source/SharpVectorRuntimeWpf/SvgObject.cs +++ b/Source/SharpVectorRuntimeWpf/SvgObject.cs @@ -16,6 +16,10 @@ public static class SvgObject DependencyProperty.RegisterAttached("Id", typeof(string), typeof(SvgObject), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.None)); + public static readonly DependencyProperty UniqueIdProperty = + DependencyProperty.RegisterAttached("UniqueId", typeof(string), typeof(SvgObject), + new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.None)); + public static readonly DependencyProperty ClassProperty = DependencyProperty.RegisterAttached("Class", typeof(string), typeof(SvgObject), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.None)); @@ -61,6 +65,16 @@ public static string GetId(DependencyObject element) return (string)element.GetValue(IdProperty); } + public static void SetUniqueId(DependencyObject element, string value) + { + element.SetValue(UniqueIdProperty, value); + } + + public static string GetUniqueId(DependencyObject element) + { + return (string)element.GetValue(UniqueIdProperty); + } + public static void SetClass(DependencyObject element, string value) { element.SetValue(ClassProperty, value);