Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / cdf / src / NetFx40 / Tools / System.Activities.Presentation / System / Activities / Presentation / MiniMap / MiniMapControl.xaml.cs / 1305376 / MiniMapControl.xaml.cs
//---------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //--------------------------------------------------------------- #if DEBUG //#define MINIMAP_DEBUG #endif namespace System.Activities.Presentation { using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; using System.Diagnostics; using System.Windows.Threading; using System.Globalization; // This class is a control displaying minimap of the attached scrollableview control // this class's functionality is limited to delegating events to minimap view controller partial class MiniMapControl : UserControl { public static readonly DependencyProperty MapSourceProperty = DependencyProperty.Register("MapSource", typeof(ScrollViewer), typeof(MiniMapControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnMapSourceChanged))); MiniMapViewController lookupWindowManager; bool isMouseDown = false; public MiniMapControl() { InitializeComponent(); this.lookupWindowManager = new MiniMapViewController(this.lookupCanvas, this.lookupWindow, this.contentGrid); } public ScrollViewer MapSource { get { return GetValue(MapSourceProperty) as ScrollViewer; } set { SetValue(MapSourceProperty, value); } } static void OnMapSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) { MiniMapControl mapControl = (MiniMapControl)sender; mapControl.lookupWindowManager.MapSource = mapControl.MapSource; } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); if (this.lookupWindowManager.StartMapLookupDrag(e)) { this.CaptureMouse(); this.isMouseDown = true; } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (this.isMouseDown) { this.lookupWindowManager.DoMapLookupDrag(e); } } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { base.OnMouseLeftButtonUp(e); if (this.isMouseDown) { Mouse.Capture(null); this.isMouseDown = false; this.lookupWindowManager.StopMapLookupDrag(); } } protected override void OnMouseDoubleClick(MouseButtonEventArgs e) { this.lookupWindowManager.CenterView(e); e.Handled = true; base.OnMouseDoubleClick(e); } protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { base.OnRenderSizeChanged(sizeInfo); this.lookupWindowManager.MapViewSizeChanged(sizeInfo); } // This class wraps positioning and calculating logic of the map view lookup window // It is also responsible for handling mouse movements internal class LookupWindow { Point mousePosition; Rectangle lookupWindowRectangle; MiniMapViewController parent; public LookupWindow(MiniMapViewController parent, Rectangle lookupWindowRectangle) { this.mousePosition = new Point(); this.parent = parent; this.lookupWindowRectangle = lookupWindowRectangle; } public double Left { get { return Canvas.GetLeft(this.lookupWindowRectangle); } set { //check if left corner is within minimap's range - clip if necessary double left = Math.Max(value - this.mousePosition.X, 0.0); //check if right corner is within minimap's range - clip if necessary left = (left + Width > this.parent.MapWidth ? this.parent.MapWidth - Width : left); //update canvas Canvas.SetLeft(this.lookupWindowRectangle, left); } } public double Top { get { return Canvas.GetTop(this.lookupWindowRectangle); } set { //check if top corner is within minimap's range - clip if necessary double top = Math.Max(value - this.mousePosition.Y, 0.0); //check if bottom corner is within minimap's range - clip if necessary top = (top + Height > this.parent.MapHeight ? this.parent.MapHeight - Height : top); //update canvas Canvas.SetTop(this.lookupWindowRectangle, top); } } public double Width { get { return this.lookupWindowRectangle.Width; } set { this.lookupWindowRectangle.Width = value; } } public double Height { get { return this.lookupWindowRectangle.Height; } set { this.lookupWindowRectangle.Height = value; } } public double MapCenterXPoint { get { return this.Left + (this.Width / 2.0); } } public double MapCenterYPoint { get { return this.Top + (this.Height / 2.0); } } public double MousePositionX { get { return this.mousePosition.X; } } public double MousePositionY { get { return this.mousePosition.Y; } } public bool IsSelected { get; private set; } public void SetPosition(double left, double top) { Left = left; Top = top; } public void SetSize(double width, double height) { Width = width; Height = height; } //whenever user clicks on the minimap, i check if clicked object is //a lookup window - if yes - i store mouse offset within the window //and mark it as selected public bool Select(object clickedItem, Point clickedPosition) { if (clickedItem == this.lookupWindowRectangle) { this.mousePosition = clickedPosition; this.IsSelected = true; } else { Unselect(); } return this.IsSelected; } public void Unselect() { this.mousePosition.X = 0; this.mousePosition.Y = 0; this.IsSelected = false; } public void Center(double x, double y) { Left = x - (Width / 2.0); Top = y - (Height / 2.0); } public void Refresh(bool unselect) { if (unselect) { Unselect(); } SetPosition(Left, Top); } } // This class is responsible for calculating size of the minimap's view area, as well as // maintaining the bi directional link between minimap and control beeing visualized. // Whenever minimap's view window position is updated, the control's content is scrolled // to calculated position // Whenever control's content is resized or scrolled, minimap reflects that change in // recalculating view's window size and/or position internal class MiniMapViewController { Canvas lookupCanvas; Grid contentGrid; ScrollViewer mapSource; LookupWindow lookupWindow; public MiniMapViewController(Canvas lookupCanvas, Rectangle lookupWindowRectangle, Grid contentGrid) { this.lookupWindow = new LookupWindow(this, lookupWindowRectangle); this.lookupCanvas = lookupCanvas; this.contentGrid = contentGrid; } public ScrollViewer MapSource { get { return this.mapSource; } set { this.mapSource = value; //calculate view's size and set initial position this.lookupWindow.Unselect(); this.CalculateLookupWindowSize(); this.lookupWindow.SetPosition(0.0, 0.0); CalculateMapPosition(this.lookupWindow.Left, this.lookupWindow.Top); this.UpdateContentGrid(); if (null != this.mapSource && null != this.mapSource.Content && this.mapSource.Content is Viewbox) { Viewbox content = (Viewbox)this.mapSource.Content; //hook up for all content size changes - handle them in OnContentSizeChanged method content.SizeChanged += (s, e) => { this.contentGrid.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { OnContentSizeChanged(s, e); })); }; //in case of scroll viewer - there are two different events to handle in one notification: this.mapSource.ScrollChanged += (s, e) => { this.contentGrid.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { //when user changes scroll position - delegate it to OnMapSourceScrollChange if (0.0 != e.HorizontalChange || 0.0 != e.VerticalChange) { OnMapSourceScrollChanged(s, e); } //when size of the scroll changes delegate it to OnContentSizeChanged if (0.0 != e.ViewportWidthChange || 0.0 != e.ViewportHeightChange) { OnContentSizeChanged(s, e); } })); }; this.OnMapSourceScrollChanged(this, null); this.OnContentSizeChanged(this, null); } } } //bunch of helper getters - used to increase algorithm readability and provide default //values, always valid values, so no additional divide-by-zero checks are neccessary public double MapWidth { get { return this.contentGrid.ActualWidth - 2 * (this.contentGrid.ColumnDefinitions[0].MinWidth); } } public double MapHeight { get { return this.contentGrid.ActualHeight - 2 * (this.contentGrid.RowDefinitions[0].MinHeight); } } internal LookupWindow LookupWindow { get { return this.lookupWindow; } } double VisibleSourceWidth { get { return (null == MapSource || 0.0 == MapSource.ViewportWidth ? 1.0 : MapSource.ViewportWidth); } } double VisibleSourceHeight { get { return (null == MapSource || 0.0 == MapSource.ViewportHeight ? 1.0 : MapSource.ViewportHeight); } } public void CenterView(MouseEventArgs args) { Point pt = args.GetPosition(this.lookupCanvas); this.lookupWindow.Unselect(); this.lookupWindow.Center(pt.X, pt.Y); CalculateMapPosition(this.lookupWindow.Left, this.lookupWindow.Top); } public void MapViewSizeChanged(SizeChangedInfo sizeInfo) { this.OnContentSizeChanged(this, EventArgs.Empty); this.lookupWindow.Unselect(); this.CalculateLookupWindowSize(); if (sizeInfo.WidthChanged && 0.0 != sizeInfo.PreviousSize.Width) { this.lookupWindow.Left = this.lookupWindow.Left * (sizeInfo.NewSize.Width / sizeInfo.PreviousSize.Width); } if (sizeInfo.HeightChanged && 0.0 != sizeInfo.PreviousSize.Height) { this.lookupWindow.Top = this.lookupWindow.Top * (sizeInfo.NewSize.Height / sizeInfo.PreviousSize.Height); } } public bool StartMapLookupDrag(MouseEventArgs args) { bool result = false; HitTestResult hitTest = VisualTreeHelper.HitTest(this.lookupCanvas, args.GetPosition(this.lookupCanvas)); if (null != hitTest && null != hitTest.VisualHit) { Point clickedPosition = args.GetPosition(hitTest.VisualHit as IInputElement); result = this.lookupWindow.Select(hitTest.VisualHit, clickedPosition); } return result; } public void StopMapLookupDrag() { this.lookupWindow.Unselect(); } public void DoMapLookupDrag(MouseEventArgs args) { if (args.LeftButton == MouseButtonState.Released && this.lookupWindow.IsSelected) { this.lookupWindow.Unselect(); } if (this.lookupWindow.IsSelected) { Point to = args.GetPosition(this.lookupCanvas); this.lookupWindow.SetPosition(to.X, to.Y); CalculateMapPosition( to.X - this.lookupWindow.MousePositionX, to.Y - this.lookupWindow.MousePositionY); } } void CalculateMapPosition(double left, double top) { if (null != MapSource && 0 != this.lookupWindow.Width && 0 != this.lookupWindow.Height) { MapSource.ScrollToHorizontalOffset((left / this.lookupWindow.Width) * VisibleSourceWidth); MapSource.ScrollToVerticalOffset((top / this.lookupWindow.Height) * VisibleSourceHeight); } } //this method calculates position of the lookup window on the minimap - it should be triggered when: // - user modifies a scroll position by draggin a scroll bar // - scroll sizes are updated by change of the srcollviewer size // - user drags minimap view - however, in this case no lookup update takes place void OnMapSourceScrollChanged(object sender, ScrollChangedEventArgs e) { if (!this.lookupWindow.IsSelected && null != MapSource) { this.lookupWindow.Unselect(); this.lookupWindow.Left = this.lookupWindow.Width * (MapSource.HorizontalOffset / VisibleSourceWidth); this.lookupWindow.Top = this.lookupWindow.Height * (MapSource.VerticalOffset / VisibleSourceHeight); } DumpData("OnMapSourceScrollChange"); } //this method calculates size and position of the minimap view - it should be triggered when: // - zoom changes // - visible size of the scrollviewer (which is map source) changes // - visible size of the minimap control changes void OnContentSizeChanged(object sender, EventArgs e) { //get old center point coordinates double centerX = this.lookupWindow.MapCenterXPoint; double centeryY = this.lookupWindow.MapCenterYPoint; //update the minimap itself this.UpdateContentGrid(); //calculate new size this.CalculateLookupWindowSize(); //try to center around old center points (window may be moved if doesn't fit) this.lookupWindow.Center(centerX, centeryY); //update scrollbars CalculateMapPosition(this.lookupWindow.Left, this.lookupWindow.Top); DumpData("OnContentSizeChanged"); } //this method calculates size of the lookup rectangle, based on the visible size of the object, //including current map width void CalculateLookupWindowSize() { double width = this.MapWidth; double height = this.MapHeight; if (this.MapSource.ScrollableWidth != 0 && this.MapSource.ExtentWidth != 0) { width = (this.MapSource.ViewportWidth / this.MapSource.ExtentWidth) * this.MapWidth; } else { //width = } if (this.MapSource.ScrollableHeight != 0 && this.MapSource.ExtentHeight != 0) { height = (this.MapSource.ViewportHeight / this.MapSource.ExtentHeight) * this.MapHeight; } this.lookupWindow.SetSize(width, height); } //this method updates content grid of the minimap - most likely, minimap view will be scaled to fit //the window - so there will be some extra space visible on the left and right sides or above and below actual //mini map view - we don't want lookup rectangle to navigate within that area, since it is not representing //actual view - we increase margins of the minimap to disallow this void UpdateContentGrid() { bool resetToDefault = true; if (this.MapSource.ExtentWidth != 0 && this.MapSource.ExtentHeight != 0) { //get width to height ratio from map source - we want to display our minimap in the same ratio double widthToHeightRatio = this.MapSource.ExtentWidth / this.MapSource.ExtentHeight; //calculate current width to height ratio on the minimap double height = this.contentGrid.ActualHeight; double width = this.contentGrid.ActualWidth; //ideally - it should be 1 - whole view perfectly fits minimap double minimapWidthToHeightRatio = (height * widthToHeightRatio)/(width > 1.0 ? width : 1.0); //if value is greater than one - we have to reduce height if (minimapWidthToHeightRatio > 1.0) { double margin = (height - (height / minimapWidthToHeightRatio))/2.0; this.contentGrid.ColumnDefinitions[0].MinWidth = 0.0; this.contentGrid.ColumnDefinitions[2].MinWidth = 0.0; this.contentGrid.RowDefinitions[0].MinHeight = margin; this.contentGrid.RowDefinitions[2].MinHeight = margin; resetToDefault = false; } //if value is less than one - we have to reduce width else if (minimapWidthToHeightRatio < 1.0) { double margin = (width - (width * minimapWidthToHeightRatio)) / 2.0; this.contentGrid.ColumnDefinitions[0].MinWidth = margin; this.contentGrid.ColumnDefinitions[2].MinWidth = margin; this.contentGrid.RowDefinitions[0].MinHeight = 0.0; this.contentGrid.RowDefinitions[2].MinHeight = 0.0; resetToDefault = false; } } //perfect match or nothing to display - no need to setup margins if (resetToDefault) { this.contentGrid.ColumnDefinitions[0].MinWidth = 0.0; this.contentGrid.ColumnDefinitions[2].MinWidth = 0.0; this.contentGrid.RowDefinitions[0].MinHeight = 0.0; this.contentGrid.RowDefinitions[2].MinHeight = 0.0; } } [Conditional("MINIMAP_DEBUG")] void DumpData(string prefix) { System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} ScrollViewer: EWidth {1}, EHeight {2}, AWidth {3}, AHeight {4}, ViewPortW {5} ViewPortH {6}", prefix, mapSource.ExtentWidth, mapSource.ExtentHeight, mapSource.ActualWidth, mapSource.ActualHeight, mapSource.ViewportWidth, mapSource.ViewportHeight)); } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- InkPresenter.cs
- XamlClipboardData.cs
- Message.cs
- StateWorkerRequest.cs
- WebExceptionStatus.cs
- Util.cs
- ValueType.cs
- SqlClientMetaDataCollectionNames.cs
- SystemTcpStatistics.cs
- AnnotationResourceChangedEventArgs.cs
- SQLCharsStorage.cs
- SqlIdentifier.cs
- XmlNodeChangedEventManager.cs
- QueryInterceptorAttribute.cs
- PerfCounters.cs
- DragEventArgs.cs
- StrongName.cs
- JsonEncodingStreamWrapper.cs
- DelegatingHeader.cs
- mediaclock.cs
- OptimizedTemplateContentHelper.cs
- AsyncContentLoadedEventArgs.cs
- DispatchProxy.cs
- MtomMessageEncoder.cs
- ExtensionSimplifierMarkupObject.cs
- CallContext.cs
- MarkupObject.cs
- Switch.cs
- ExcCanonicalXml.cs
- LinkLabel.cs
- NamespaceList.cs
- LinearGradientBrush.cs
- WsiProfilesElementCollection.cs
- MouseEvent.cs
- LineInfo.cs
- ToolStripCustomTypeDescriptor.cs
- TextLineResult.cs
- CodeObjectCreateExpression.cs
- WebPartRestoreVerb.cs
- RoleExceptions.cs
- WmlImageAdapter.cs
- GeneralTransform3DGroup.cs
- XmlNamedNodeMap.cs
- FreezableDefaultValueFactory.cs
- XsdDuration.cs
- PeerService.cs
- SqlSelectStatement.cs
- WindowsIPAddress.cs
- CryptoSession.cs
- DataObjectMethodAttribute.cs
- DataRow.cs
- TextBoxAutoCompleteSourceConverter.cs
- NextPreviousPagerField.cs
- OleDbConnection.cs
- DelegatingConfigHost.cs
- SqlInternalConnectionTds.cs
- ContentElementAutomationPeer.cs
- Events.cs
- ItemsPresenter.cs
- DashStyle.cs
- RemoteDebugger.cs
- Grid.cs
- UnsafeNativeMethods.cs
- StringExpressionSet.cs
- ValidationEventArgs.cs
- ToolStripTextBox.cs
- WebContext.cs
- BinaryHeap.cs
- MultipleViewPattern.cs
- RSACryptoServiceProvider.cs
- CommonProperties.cs
- DateTimePicker.cs
- ChannelManager.cs
- UnsafeNativeMethodsTablet.cs
- CodeNamespaceImport.cs
- dataSvcMapFileLoader.cs
- XmlElementList.cs
- XmlNamespaceMappingCollection.cs
- CodeMemberEvent.cs
- AnnotationAdorner.cs
- DataGridViewRowContextMenuStripNeededEventArgs.cs
- AlternateViewCollection.cs
- DurableMessageDispatchInspector.cs
- WebPartExportVerb.cs
- PropertyTabAttribute.cs
- EntityUtil.cs
- DataGridViewSelectedCellCollection.cs
- Misc.cs
- LocalizationParserHooks.cs
- XmlDictionaryReader.cs
- ContainerFilterService.cs
- XPathEmptyIterator.cs
- BindingGraph.cs
- ReadOnlyState.cs
- Trace.cs
- FrameworkContentElementAutomationPeer.cs
- Wildcard.cs
- VersionUtil.cs
- KerberosRequestorSecurityTokenAuthenticator.cs
- SQLBytes.cs