markus / ZoomAndPan / ZoomAndPanControl.cs @ f87ee33e
이력 | 보기 | 이력해설 | 다운로드 (36.5 KB)
1 | 787a4489 | KangIngu | using System; |
---|---|---|---|
2 | using System.Collections.Generic; |
||
3 | using System.Linq; |
||
4 | using System.Text; |
||
5 | using System.Threading.Tasks; |
||
6 | using System.Windows; |
||
7 | using System.Windows.Controls; |
||
8 | using System.Windows.Media; |
||
9 | |||
10 | /// 출처 : https://www.codeproject.com/Articles/85603/A-WPF-custom-control-for-zooming-and-panning#ZoomAndPanControlMethods |
||
11 | namespace ZoomAndPan |
||
12 | { |
||
13 | public partial class ZoomAndPanControl : ContentControl |
||
14 | { |
||
15 | /// <summary> |
||
16 | /// Reference to the underlying content, which is named PART_Content in the template. |
||
17 | /// </summary> |
||
18 | private FrameworkElement content; |
||
19 | |||
20 | /// <summary> |
||
21 | /// The transform that is applied to the content to scale it by 'ContentScale' |
||
22 | /// </summary> |
||
23 | private ScaleTransform contentScaleTransform = null; |
||
24 | |||
25 | /// <summary> |
||
26 | /// The transform that is applied to the content to offset it by 'ContentOffsetX' and 'ContentOffsetY' |
||
27 | /// </summary> |
||
28 | private TranslateTransform contentOffsetTransform = null; |
||
29 | |||
30 | /// <summary> |
||
31 | /// Enable the udate of the content offset as the content scale changes. |
||
32 | /// This enabled for zooming about a point (google-maps stlye zooming) and zooming to a rect |
||
33 | /// </summary> |
||
34 | private bool enableContentOffsetUpdateFromScale = false; |
||
35 | |||
36 | private bool disableScrollOffsetSync = false; |
||
37 | |||
38 | //public double UnScaledExtentWidth |
||
39 | //{ |
||
40 | // get |
||
41 | // { |
||
42 | // return (double)GetValue(UnScaledExtentWidthProperty); |
||
43 | // } |
||
44 | // set |
||
45 | // { |
||
46 | // SetValue(UnScaledExtentWidthProperty, value); |
||
47 | // } |
||
48 | //} |
||
49 | |||
50 | //public double UnScaledExtentHeight |
||
51 | //{ |
||
52 | // get |
||
53 | // { |
||
54 | // return (double)GetValue(UnScaledExtentHeightProperty); |
||
55 | // } |
||
56 | // set |
||
57 | // { |
||
58 | // SetValue(UnScaledExtentHeightProperty, value); |
||
59 | // } |
||
60 | //} |
||
61 | |||
62 | ///// <summary> |
||
63 | ///// The width of the viewport in content coordinate, clamped to the width of the content. |
||
64 | ///// </summary> |
||
65 | public double ConstrainedContentViewportWidth |
||
66 | { |
||
67 | get |
||
68 | { |
||
69 | return (double)GetValue(ConstrainedContentViewportWidthProperty); |
||
70 | } |
||
71 | set |
||
72 | { |
||
73 | SetValue(ConstrainedContentViewportWidthProperty, value); |
||
74 | } |
||
75 | } |
||
76 | |||
77 | ///// <summary> |
||
78 | ///// The height of the viewport in content coordinate, clamped to the height of the content. |
||
79 | ///// </summary> |
||
80 | public double ConstrainedContentViewportHeight |
||
81 | { |
||
82 | get |
||
83 | { |
||
84 | return (double)GetValue(ConstrainedContentViewportHeightProperty); |
||
85 | } |
||
86 | set |
||
87 | { |
||
88 | SetValue(ConstrainedContentViewportHeightProperty, value); |
||
89 | } |
||
90 | } |
||
91 | |||
92 | #region IScrollInfo |
||
93 | /// <summary> |
||
94 | /// Set to 'true' when the vertical scrollbar is enabled. |
||
95 | /// </summary> |
||
96 | private bool canVerticallyScroll = false; |
||
97 | |||
98 | /// <summary> |
||
99 | /// Set to 'true' when the vertical scrollbar is enabled. |
||
100 | /// </summary> |
||
101 | private bool canHorizontallyScroll = false; |
||
102 | |||
103 | /// <summary> |
||
104 | /// Reference to the ScrollViewer that is wrapped (in XAML) around the ZoomAndPanControl. |
||
105 | /// Or set to null if there is no ScrollViewer. |
||
106 | /// </summary> |
||
107 | private ScrollViewer scrollOwner; |
||
108 | |||
109 | /// <summary> |
||
110 | /// Records the unscaled extent of the content. |
||
111 | /// This is calculated during the measure and arrange. |
||
112 | /// </summary> |
||
113 | private Size unScaledExtent = new Size(); |
||
114 | |||
115 | /// <summary> |
||
116 | /// Records the size of the viewport (in viewport coordinates) onto the content. |
||
117 | /// This is calculated during the measure and arrange. |
||
118 | /// </summary> |
||
119 | private Size viewport = new Size(0, 0); |
||
120 | |||
121 | #endregion |
||
122 | |||
123 | #region Properties |
||
124 | /// <summary> |
||
125 | /// Get/set the current scale (or zoom factor) of the content. |
||
126 | /// </summary> |
||
127 | public double ContentScale |
||
128 | { |
||
129 | get |
||
130 | { |
||
131 | return (double)GetValue(ContentScaleProperty); |
||
132 | } |
||
133 | set |
||
134 | { |
||
135 | SetValue(ContentScaleProperty, value); |
||
136 | } |
||
137 | } |
||
138 | |||
139 | /// <summary> |
||
140 | /// Get/set the minimum value for 'ContentScale' |
||
141 | /// </summary> |
||
142 | public double MinContentScale |
||
143 | { |
||
144 | get |
||
145 | { |
||
146 | return (double)GetValue(MinContentScaleProperty); |
||
147 | } |
||
148 | set |
||
149 | { |
||
150 | SetValue(MinContentScaleProperty, value); |
||
151 | } |
||
152 | } |
||
153 | |||
154 | /// <summary> |
||
155 | /// Get/set the maximum value for 'ContentScale' |
||
156 | /// </summary> |
||
157 | public double MaxContentScale |
||
158 | { |
||
159 | get |
||
160 | { |
||
161 | return (double)GetValue(MaxContentScaleProperty); |
||
162 | } |
||
163 | set |
||
164 | { |
||
165 | SetValue(MaxContentScaleProperty, value); |
||
166 | } |
||
167 | } |
||
168 | |||
169 | /// <summary> |
||
170 | /// Get the viewport width, in content coordinates. |
||
171 | /// </summary> |
||
172 | public double ContentViewportWidth |
||
173 | { |
||
174 | get |
||
175 | { |
||
176 | return (double)GetValue(ContentViewportWidthProperty); |
||
177 | } |
||
178 | set |
||
179 | { |
||
180 | SetValue(ContentViewportWidthProperty, value); |
||
181 | } |
||
182 | } |
||
183 | |||
184 | /// <summary> |
||
185 | /// Get the viewport height, in content coordinates |
||
186 | /// </summary> |
||
187 | public double ContentViewportHeight |
||
188 | { |
||
189 | get |
||
190 | { |
||
191 | return (double)GetValue(ContentViewportHeightProperty); |
||
192 | } |
||
193 | set |
||
194 | { |
||
195 | SetValue(ContentViewportHeightProperty, value); |
||
196 | } |
||
197 | } |
||
198 | |||
199 | /// <summary> |
||
200 | /// Get/set the X offset (in content coordinates) of the view on the content. |
||
201 | /// </summary> |
||
202 | public double ContentOffsetX |
||
203 | { |
||
204 | get |
||
205 | { |
||
206 | return (double)GetValue(ContentOffsetXProperty); |
||
207 | } |
||
208 | set |
||
209 | { |
||
210 | SetValue(ContentOffsetXProperty, value); |
||
211 | } |
||
212 | } |
||
213 | |||
214 | /// <summary> |
||
215 | /// Get/Set the offset (in content coordinates) of the view on the content |
||
216 | /// </summary> |
||
217 | public double ContentOffsetY |
||
218 | { |
||
219 | get |
||
220 | { |
||
221 | return (double)GetValue(ContentOffsetYProperty); |
||
222 | } |
||
223 | set |
||
224 | { |
||
225 | SetValue(ContentOffsetYProperty, value); |
||
226 | } |
||
227 | } |
||
228 | |||
229 | /// <summary> |
||
230 | /// ContentOffsetTransformX is TranslateTranform value |
||
231 | /// </summary> |
||
232 | public double ContentOffsetTransformX |
||
233 | { |
||
234 | get |
||
235 | { |
||
236 | return (double)GetValue(ContentOffsetTransformXProperty); |
||
237 | } |
||
238 | set |
||
239 | { |
||
240 | SetValue(ContentOffsetTransformXProperty, value); |
||
241 | } |
||
242 | } |
||
243 | |||
244 | /// <summary> |
||
245 | /// ContentOffsetTransformY is TranslateTranform value |
||
246 | /// </summary> |
||
247 | public double ContentOffsetTransformY |
||
248 | { |
||
249 | get |
||
250 | { |
||
251 | return (double)GetValue(ContentOffsetTransformYProperty); |
||
252 | } |
||
253 | set |
||
254 | { |
||
255 | SetValue(ContentOffsetTransformYProperty, value); |
||
256 | } |
||
257 | } |
||
258 | |||
259 | /// <summary> |
||
260 | /// unScaledExtentWidth * ContentScale |
||
261 | /// </summary> |
||
262 | public double ScaledContentWidth |
||
263 | { |
||
264 | get |
||
265 | { |
||
266 | return (double)GetValue(ScaledContentWidthProperty); |
||
267 | } |
||
268 | set |
||
269 | { |
||
270 | SetValue(ScaledContentWidthProperty, value); |
||
271 | } |
||
272 | } |
||
273 | |||
274 | /// <summary> |
||
275 | /// unScaledExtentHeight * ContentScale |
||
276 | /// </summary> |
||
277 | public double ScaledContentHeight |
||
278 | { |
||
279 | get |
||
280 | { |
||
281 | return (double)GetValue(ScaledContentHeightProperty); |
||
282 | } |
||
283 | set |
||
284 | { |
||
285 | SetValue(ScaledContentHeightProperty, value); |
||
286 | } |
||
287 | } |
||
288 | |||
289 | /// <summary> |
||
290 | /// The X coordinate of the content focus, this is the point that we are focusing on when zooming. |
||
291 | /// </summary> |
||
292 | public double ContentZoomFocusX |
||
293 | { |
||
294 | get |
||
295 | { |
||
296 | return (double)GetValue(ContentZoomFocusXProperty); |
||
297 | } |
||
298 | set |
||
299 | { |
||
300 | SetValue(ContentZoomFocusXProperty, value) ; |
||
301 | } |
||
302 | } |
||
303 | |||
304 | /// <summary> |
||
305 | /// The Y coordinate of the content focus, this is the point that we are focusing on when zooming. |
||
306 | /// </summary> |
||
307 | public double ContentZoomFocusY |
||
308 | { |
||
309 | get |
||
310 | { |
||
311 | return (double)GetValue(ContentZoomFocusYProperty); |
||
312 | } |
||
313 | set |
||
314 | { |
||
315 | SetValue(ContentZoomFocusYProperty,value); |
||
316 | } |
||
317 | } |
||
318 | |||
319 | public double RotationAngle |
||
320 | { |
||
321 | get { return (double)GetValue(RotationAngleProperty); } |
||
322 | |||
323 | set { SetValue(RotationAngleProperty, value); } |
||
324 | } |
||
325 | |||
326 | public bool IsMouseWheelScrollingEnabled |
||
327 | { |
||
328 | get |
||
329 | { |
||
330 | return (bool)GetValue(IsMouseWheelScrollingEnabledProperty); |
||
331 | } |
||
332 | set |
||
333 | { |
||
334 | SetValue(IsMouseWheelScrollingEnabledProperty, value); |
||
335 | } |
||
336 | |||
337 | } |
||
338 | |||
339 | public double AnimationDuration |
||
340 | { |
||
341 | get |
||
342 | { |
||
343 | return (double)GetValue(AnimationDurationProperty); |
||
344 | } |
||
345 | set |
||
346 | { |
||
347 | SetValue(AnimationDurationProperty, value); |
||
348 | } |
||
349 | } |
||
350 | |||
351 | /// <summary> |
||
352 | /// The X coordinate of the viewport focus, this is the point in the viewport (in viewport coordinates) |
||
353 | /// that the content focus point is locked to while zooming in. |
||
354 | /// </summary> |
||
355 | public double ViewportZoomFocusX |
||
356 | { |
||
357 | get |
||
358 | { |
||
359 | return (double)GetValue(ViewportZoomFocusXProperty); |
||
360 | } |
||
361 | set |
||
362 | { |
||
363 | SetValue(ViewportZoomFocusXProperty, value); |
||
364 | } |
||
365 | } |
||
366 | |||
367 | /// <summary> |
||
368 | /// The Y coordinate of the viewport focus, this is the point in the viewport (in viewport coordinates) |
||
369 | /// that the content focus point is locked to while zooming in. |
||
370 | /// </summary> |
||
371 | public double ViewportZoomFocusY |
||
372 | { |
||
373 | get |
||
374 | { |
||
375 | return (double)GetValue(ViewportZoomFocusYProperty); |
||
376 | } |
||
377 | set |
||
378 | { |
||
379 | SetValue(ViewportZoomFocusYProperty, value); |
||
380 | } |
||
381 | } |
||
382 | |||
383 | public ImageBrush BackgroundImage |
||
384 | { |
||
385 | get { return (ImageBrush)GetValue(BackgroundImageProperty); } |
||
386 | set { SetValue(BackgroundImageProperty, value); } |
||
387 | } |
||
388 | |||
389 | #endregion Properties |
||
390 | |||
391 | #region Internal Method |
||
392 | static ZoomAndPanControl() |
||
393 | { |
||
394 | DefaultStyleKeyProperty.OverrideMetadata(typeof(ZoomAndPanControl), |
||
395 | new FrameworkPropertyMetadata(typeof(ZoomAndPanControl))); |
||
396 | } |
||
397 | |||
398 | public override void OnApplyTemplate() |
||
399 | { |
||
400 | base.OnApplyTemplate(); |
||
401 | |||
402 | content = this.Template.FindName("PART_Content", this) as FrameworkElement; |
||
403 | |||
404 | if (content != null) |
||
405 | { |
||
406 | |||
407 | this.contentScaleTransform = new ScaleTransform(ContentScale, ContentScale); |
||
408 | this.contentOffsetTransform = new TranslateTransform(); |
||
409 | TransformGroup transformGroup = new TransformGroup(); |
||
410 | transformGroup.Children.Add(this.contentOffsetTransform); |
||
411 | transformGroup.Children.Add(this.contentScaleTransform); |
||
412 | content.RenderTransform = transformGroup; |
||
413 | } |
||
414 | } |
||
415 | |||
416 | protected override Size MeasureOverride(Size constraint) |
||
417 | { |
||
418 | //System.Diagnostics. Debug.WriteLine(Name); |
||
419 | Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); |
||
420 | Size childSize = base.MeasureOverride(infiniteSize); |
||
421 | //Size childSize = new Size(1024,1754); |
||
422 | |||
423 | if (childSize != unScaledExtent ) |
||
424 | { |
||
425 | unScaledExtent = childSize; |
||
426 | |||
427 | //unScaledExtent가 결정된 이후 UpdateViewportSize가 일어나야한다. |
||
428 | UpdateViewportSize(constraint); |
||
429 | |||
430 | } |
||
431 | |||
432 | |||
433 | double width = constraint.Width; |
||
434 | double height = constraint.Height; |
||
435 | |||
436 | if (double.IsPositiveInfinity(width)) |
||
437 | width = childSize.Width; |
||
438 | |||
439 | if (double.IsPositiveInfinity(height)) |
||
440 | height = childSize.Height; |
||
441 | |||
442 | if (scrollOwner != null) |
||
443 | scrollOwner.InvalidateScrollInfo(); |
||
444 | |||
445 | |||
446 | return new Size(width, height); |
||
447 | } |
||
448 | |||
449 | protected override Size ArrangeOverride(Size arrangeBounds) |
||
450 | { |
||
451 | Size size = base.ArrangeOverride(arrangeBounds); |
||
452 | |||
453 | //unScaledExtent가 결정된 이후 UpdateViewportSize가 일어나야한다. |
||
454 | if (content.DesiredSize != unScaledExtent) |
||
455 | { |
||
456 | unScaledExtent = content.DesiredSize; |
||
457 | |||
458 | if (scrollOwner != null) |
||
459 | scrollOwner.InvalidateScrollInfo(); |
||
460 | } |
||
461 | |||
462 | |||
463 | UpdateViewportSize(arrangeBounds); |
||
464 | |||
465 | return size; |
||
466 | } |
||
467 | #endregion |
||
468 | |||
469 | #region Dependency Properties |
||
470 | |||
471 | #region Max & Min ContentScale |
||
472 | |||
473 | public static readonly DependencyProperty ContentScaleProperty = |
||
474 | DependencyProperty.Register("ContentScale", typeof(double), typeof(ZoomAndPanControl), |
||
475 | new FrameworkPropertyMetadata(1.0, ContentScale_PropertyChanged, ContentScale_Coerce)); |
||
476 | |||
477 | public static readonly DependencyProperty MinContentScaleProperty = |
||
478 | DependencyProperty.Register("MinContentScale", typeof(double), typeof(ZoomAndPanControl), |
||
479 | new FrameworkPropertyMetadata(0.01, MinOrMaxContentScale_PropertyChanged)); |
||
480 | |||
481 | public static readonly DependencyProperty MaxContentScaleProperty = |
||
482 | DependencyProperty.Register("MaxContentScale", typeof(double), typeof(ZoomAndPanControl), |
||
483 | new FrameworkPropertyMetadata(10.0, MinOrMaxContentScale_PropertyChanged)); |
||
484 | |||
485 | #endregion Max & Min ContentScale |
||
486 | |||
487 | #region ContentOffset X & Y |
||
488 | public static readonly DependencyProperty ContentOffsetXProperty = |
||
489 | DependencyProperty.Register("ContentOffsetX", typeof(double), typeof(ZoomAndPanControl), |
||
490 | new FrameworkPropertyMetadata(0.0, ContentOffsetX_PropertyChanged, ContentOffsetX_Coerce)); |
||
491 | |||
492 | public static readonly DependencyProperty ContentOffsetYProperty = |
||
493 | DependencyProperty.Register("ContentOffsetY", typeof(double), typeof(ZoomAndPanControl), |
||
494 | new FrameworkPropertyMetadata(0.0, ContentOffsetY_PropertyChanged, ContentOffsetY_Coerce)); |
||
495 | #endregion ContentOffset X & Y |
||
496 | |||
497 | #region ContentViewportProperty |
||
498 | public static readonly DependencyProperty ContentViewportHeightProperty = |
||
499 | DependencyProperty.Register("ContentViewportHeight", typeof(double), typeof(ZoomAndPanControl), |
||
500 | new FrameworkPropertyMetadata(0.0)); |
||
501 | |||
502 | public static readonly DependencyProperty ContentViewportWidthProperty = |
||
503 | DependencyProperty.Register("ContentViewportWidth", typeof(double), typeof(ZoomAndPanControl), |
||
504 | new FrameworkPropertyMetadata(0.0)); |
||
505 | #endregion ContentViewportProperty |
||
506 | |||
507 | #region TranslateTransform |
||
508 | public static readonly DependencyProperty ContentOffsetTransformXProperty = |
||
509 | DependencyProperty.Register("ContentOffsetTransformX", typeof(double), typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0, ContentOffsetTransformXPropertyChanged)); |
||
510 | |||
511 | public static readonly DependencyProperty ContentOffsetTransformYProperty = |
||
512 | DependencyProperty.Register("ContentOffsetTransformY", typeof(double), typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0, ContentOffsetTransformYPropertyChanged)); |
||
513 | #endregion |
||
514 | |||
515 | #region ContentZoomFocus X & Y |
||
516 | public static readonly DependencyProperty ContentZoomFocusXProperty = |
||
517 | DependencyProperty.Register("ContentZoomFocusX", typeof(double), typeof(ZoomAndPanControl), |
||
518 | new FrameworkPropertyMetadata(0.0)); |
||
519 | |||
520 | public static readonly DependencyProperty ContentZoomFocusYProperty = |
||
521 | DependencyProperty.Register("ContentZoomFocusY", typeof(double), typeof(ZoomAndPanControl), |
||
522 | new FrameworkPropertyMetadata(0.0)); |
||
523 | #endregion |
||
524 | |||
525 | #region ScaleContentProperty |
||
526 | |||
527 | public static readonly DependencyProperty ScaledContentWidthProperty = |
||
528 | DependencyProperty.Register("ScaledContentWidth", typeof(double), |
||
529 | typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0)); |
||
530 | |||
531 | public static readonly DependencyProperty ScaledContentHeightProperty = |
||
532 | DependencyProperty.Register("ScaledContentHeight", typeof(double), |
||
533 | typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0)); |
||
534 | #endregion ScaleContentProperty |
||
535 | |||
536 | #region ConstrainedContentViewport Width, Height |
||
537 | public static readonly DependencyProperty ConstrainedContentViewportHeightProperty = |
||
538 | DependencyProperty.Register("ConstrainedContentViewportHeight", typeof(double), |
||
539 | typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0)); |
||
540 | |||
541 | public static readonly DependencyProperty ConstrainedContentViewportWidthProperty = |
||
542 | DependencyProperty.Register("ConstrainedContentViewportWidth", typeof(double), |
||
543 | typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0)); |
||
544 | #endregion |
||
545 | |||
546 | #region Unscaled Width, Height |
||
547 | // public static readonly DependencyProperty UnScaledExtentWidthProperty = |
||
548 | //DependencyProperty.Register("UnScaledExtentWidth", typeof(double), |
||
549 | //typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0)); |
||
550 | |||
551 | // public static readonly DependencyProperty UnScaledExtentHeightProperty = |
||
552 | // DependencyProperty.Register("UnScaledExtentHeight", typeof(double), |
||
553 | // typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0)); |
||
554 | #endregion |
||
555 | |||
556 | |||
557 | #region RotationAngle |
||
558 | public static readonly DependencyProperty |
||
559 | RotationAngleProperty = |
||
560 | DependencyProperty.Register("RotationAngle", |
||
561 | typeof(double), typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(0.0)); |
||
562 | #endregion |
||
563 | |||
564 | #region IsMouseWheelScrolling |
||
565 | public static readonly DependencyProperty IsMouseWheelScrollingEnabledProperty = |
||
566 | DependencyProperty.Register("IsMouseWheelScrollingEnabled", typeof(bool), typeof(ZoomAndPanControl), new FrameworkPropertyMetadata(false)); |
||
567 | #endregion IsMouseWheelScrolling |
||
568 | |||
569 | #region AnimationDuration |
||
570 | public static readonly DependencyProperty AnimationDurationProperty = |
||
571 | DependencyProperty.Register("AnimationDuration", typeof(double), typeof(ZoomAndPanControl), |
||
572 | new FrameworkPropertyMetadata(0.4)); |
||
573 | |||
574 | #endregion |
||
575 | |||
576 | #region ViewportZoomFocus |
||
577 | public static readonly DependencyProperty ViewportZoomFocusXProperty = |
||
578 | DependencyProperty.Register("ViewportZoomFocusX", typeof(double), typeof(ZoomAndPanControl), |
||
579 | new FrameworkPropertyMetadata(0.0)); |
||
580 | |||
581 | public static readonly DependencyProperty ViewportZoomFocusYProperty = |
||
582 | DependencyProperty.Register("ViewportZoomFocusY", typeof(double), typeof(ZoomAndPanControl), |
||
583 | new FrameworkPropertyMetadata(0.0)); |
||
584 | |||
585 | #endregion |
||
586 | |||
587 | |||
588 | // Using a DependencyProperty as the backing store for BackGroundImage. This enables animation, styling, binding, etc... |
||
589 | public static readonly DependencyProperty BackgroundImageProperty = |
||
590 | DependencyProperty.Register("BackgroundImage", typeof(ImageBrush), typeof(ZoomAndPanControl), null); |
||
591 | |||
592 | |||
593 | #endregion Dependency Properties |
||
594 | |||
595 | #region Dependency PropertyChangedEvent |
||
596 | |||
597 | #region TranslateTransform |
||
598 | private static void ContentOffsetTransformXPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) |
||
599 | { |
||
600 | var zoomAndPanControl = (ZoomAndPanControl)d; |
||
601 | |||
602 | if (zoomAndPanControl != null) |
||
603 | zoomAndPanControl.SetContentOffsetTransformX(); |
||
604 | } |
||
605 | |||
606 | |||
607 | private static void ContentOffsetTransformYPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) |
||
608 | { |
||
609 | var zoomAndPanControl = (ZoomAndPanControl)d; |
||
610 | |||
611 | if (zoomAndPanControl != null) |
||
612 | zoomAndPanControl.SetContentOffsetTransformY(); |
||
613 | } |
||
614 | #endregion |
||
615 | |||
616 | #region ScaleContentProperty |
||
617 | /// <summary> |
||
618 | /// Method called to clamp the 'ContentScale' value to its valid range. |
||
619 | /// </summary> |
||
620 | private static object ContentScale_Coerce(DependencyObject d, object baseValue) |
||
621 | { |
||
622 | ZoomAndPanControl c = (ZoomAndPanControl)d; |
||
623 | double value = (double)baseValue; |
||
624 | value = Math.Min(Math.Max(value, c.MinContentScale), c.MaxContentScale); |
||
625 | return value; |
||
626 | } |
||
627 | |||
628 | /// <summary> |
||
629 | /// Event raised when the 'ContentScale' property has changed value. |
||
630 | /// </summary> |
||
631 | private static void ContentScale_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) |
||
632 | { |
||
633 | ZoomAndPanControl c = (ZoomAndPanControl)o; |
||
634 | |||
635 | if (c.contentScaleTransform != null) |
||
636 | { |
||
637 | c.contentScaleTransform.ScaleX = c.ContentScale; |
||
638 | c.contentScaleTransform.ScaleY = c.ContentScale; |
||
639 | } |
||
640 | |||
641 | c.UpdateContentViewportSize(); |
||
642 | |||
643 | c.UpdateContentZoomFocusX(); |
||
644 | c.UpdateContentZoomFocusY(); |
||
645 | |||
646 | |||
647 | if(c.enableContentOffsetUpdateFromScale) |
||
648 | { |
||
649 | try |
||
650 | { |
||
651 | double viewportOffsetX = c.ViewportZoomFocusX - (c.ViewportWidth / 2); |
||
652 | double viewportOffsetY = c.ViewportZoomFocusY - (c.ViewportHeight / 2); |
||
653 | double contentOffsetX = viewportOffsetX / c.ContentScale; |
||
654 | double contentOffsetY = viewportOffsetY / c.ContentScale; |
||
655 | c.ContentOffsetX = (c.ContentZoomFocusX - (c.ContentViewportWidth / 2)) - contentOffsetX; |
||
656 | c.ContentOffsetY = (c.ContentZoomFocusY - (c.ContentViewportHeight / 2)) - contentOffsetY; |
||
657 | } |
||
658 | finally |
||
659 | { |
||
660 | c.enableContentOffsetUpdateFromScale = false; |
||
661 | } |
||
662 | } |
||
663 | |||
664 | if (c.scrollOwner != null) |
||
665 | { |
||
666 | c.scrollOwner.InvalidateScrollInfo(); |
||
667 | } |
||
668 | |||
669 | } |
||
670 | #endregion |
||
671 | |||
672 | #region ContentOffset X & Y |
||
673 | |||
674 | /// <summary> |
||
675 | /// Method called to clamp the 'ContentOffsetX' value to its valid range. |
||
676 | /// </summary> |
||
677 | private static object ContentOffsetX_Coerce(DependencyObject d, object baseValue) |
||
678 | { |
||
679 | ZoomAndPanControl c = (ZoomAndPanControl)d; |
||
680 | double value = (double)baseValue; |
||
681 | double minOffsetX = 0.0; |
||
682 | double maxOffsetX = Math.Max(0.0, c.unScaledExtent.Width - c.ConstrainedContentViewportWidth); |
||
683 | value = Math.Min(Math.Max(value, minOffsetX), maxOffsetX); |
||
684 | return value; |
||
685 | } |
||
686 | |||
687 | /// <summary> |
||
688 | /// Event raised when the 'ContentOffsetX' property has changed value. |
||
689 | /// </summary> |
||
690 | private static void ContentOffsetX_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) |
||
691 | { |
||
692 | ZoomAndPanControl c = (ZoomAndPanControl)o; |
||
693 | |||
694 | c.UpdateTranslationX(); |
||
695 | |||
696 | if (c.scrollOwner != null) |
||
697 | { |
||
698 | c.scrollOwner.InvalidateScrollInfo(); |
||
699 | } |
||
700 | } |
||
701 | |||
702 | /// <summary> |
||
703 | /// Method called to clamp the 'ContentOffsetY' value to its valid range. |
||
704 | /// </summary> |
||
705 | private static object ContentOffsetY_Coerce(DependencyObject d, object baseValue) |
||
706 | { |
||
707 | ZoomAndPanControl c = (ZoomAndPanControl)d; |
||
708 | double value = (double)baseValue; |
||
709 | double minOffsetY = 0.0; |
||
710 | double maxOffsetY = Math.Max(0.0, c.unScaledExtent.Height - c.ConstrainedContentViewportHeight); |
||
711 | value = Math.Min(Math.Max(value, minOffsetY), maxOffsetY); |
||
712 | return value; |
||
713 | } |
||
714 | |||
715 | /// <summary> |
||
716 | /// Event raised when the 'ContentOffsetY' property has changed value. |
||
717 | /// </summary> |
||
718 | private static void ContentOffsetY_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) |
||
719 | { |
||
720 | |||
721 | ZoomAndPanControl c = (ZoomAndPanControl)o; |
||
722 | c.UpdateTranslationY(); |
||
723 | |||
724 | if (c.scrollOwner != null) |
||
725 | { |
||
726 | c.scrollOwner.InvalidateScrollInfo(); |
||
727 | } |
||
728 | } |
||
729 | |||
730 | |||
731 | public void AnimatedZoomTo(Rect contentRect) |
||
732 | { |
||
733 | var ScaleX = this.ContentViewportWidth / contentRect.Width; |
||
734 | var ScaleY = this.ContentViewportHeight / contentRect.Height; |
||
735 | var NewContentScale = this.ContentScale * Math.Min(ScaleX, ScaleY); |
||
736 | ContentZoomFocusX = contentRect.X + (contentRect.Width / 2); |
||
737 | ContentZoomFocusY = contentRect.Y + (contentRect.Height/ 2); |
||
738 | AnimatedZoomPointToViewportCenter(NewContentScale, new Point(ContentZoomFocusX, ContentZoomFocusY), null); |
||
739 | } |
||
740 | |||
741 | /// <summary> |
||
742 | /// Zoom to the specified scale and move the specified focus point to the center of the viewport. |
||
743 | /// </summary> |
||
744 | private void AnimatedZoomPointToViewportCenter(double newContentScale, Point contentZoomFocus, EventHandler callback) |
||
745 | { |
||
746 | newContentScale = Math.Min(Math.Max(newContentScale, MinContentScale), MaxContentScale); |
||
747 | |||
748 | ContentZoomFocusX = contentZoomFocus.X; |
||
749 | ContentZoomFocusY = contentZoomFocus.Y; |
||
750 | |||
751 | enableContentOffsetUpdateFromScale = true; |
||
752 | |||
753 | AnimationHelper.StartAnimation(this, ContentScaleProperty, newContentScale, AnimationDuration, |
||
754 | delegate(object sender, EventArgs e ){ |
||
755 | enableContentOffsetUpdateFromScale = false; |
||
756 | if(callback != null) |
||
757 | { |
||
758 | callback(this, EventArgs.Empty); |
||
759 | } |
||
760 | }); |
||
761 | } |
||
762 | |||
763 | /// <summary> |
||
764 | /// Do animation that scales the content so that it fits completely in the control. |
||
765 | /// </summary> |
||
766 | public void AnimatedScaleToFit() |
||
767 | { |
||
768 | if (content == null) |
||
769 | { |
||
770 | throw new ApplicationException("PART_Content was not found in the ZoomAndPanControl visual template!"); |
||
771 | } |
||
772 | |||
773 | AnimatedZoomTo(new Rect(0, 0, content.ActualWidth, content.ActualHeight)); |
||
774 | } |
||
775 | |||
776 | #endregion Dependency PropertyChangedEvent |
||
777 | |||
778 | |||
779 | #region Max & Min ContentScale |
||
780 | private static void MinOrMaxContentScale_PropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) |
||
781 | { |
||
782 | ZoomAndPanControl c = (ZoomAndPanControl)o; |
||
783 | c.ContentScale = Math.Min(Math.Max(c.ContentScale, c.MinContentScale), c.MaxContentScale); |
||
784 | } |
||
785 | #endregion |
||
786 | |||
787 | #endregion |
||
788 | |||
789 | /// <summary> |
||
790 | /// Set TranslateTransform X Coordinate |
||
791 | /// </summary> |
||
792 | public void SetContentOffsetTransformX() |
||
793 | { |
||
794 | this.contentOffsetTransform.X = ContentOffsetTransformX; |
||
795 | } |
||
796 | |||
797 | /// <summary> |
||
798 | /// Set TranslateTransform Y Coordinate |
||
799 | /// </summary> |
||
800 | public void SetContentOffsetTransformY() |
||
801 | { |
||
802 | this.contentOffsetTransform.Y = ContentOffsetTransformY; |
||
803 | } |
||
804 | |||
805 | /// <summary> |
||
806 | /// Update the Y coordinate of the translation transformation. |
||
807 | /// </summary> |
||
808 | private void UpdateTranslationY() |
||
809 | { |
||
810 | if(this.contentOffsetTransform != null) |
||
811 | { |
||
812 | ///Canvas의 Height 값 * ContentScaled 수치를 곱하여 scaledContentHeight를 구한다. |
||
813 | ScaledContentHeight = this.unScaledExtent.Height * this.ContentScale; |
||
814 | |||
815 | ////Viewport보다 scaledContentHeight 값이 작다면 |
||
816 | if (ScaledContentHeight < this.viewport.Height) |
||
817 | { |
||
818 | this.ContentOffsetTransformY = (ContentViewportHeight - this.unScaledExtent.Height) / 2; |
||
819 | } |
||
820 | else |
||
821 | { |
||
822 | this.ContentOffsetTransformY = -this.ContentOffsetY; |
||
823 | } |
||
824 | |||
825 | this.ContentOffsetY = this.ContentOffsetY; |
||
826 | } |
||
827 | } |
||
828 | |||
829 | /// <summary> |
||
830 | /// Update the X coordinate of the translation transformation. |
||
831 | /// </summary> |
||
832 | private void UpdateTranslationX() |
||
833 | { |
||
834 | if (this.contentOffsetTransform != null) |
||
835 | { |
||
836 | ScaledContentWidth = this.unScaledExtent.Width * this.ContentScale; |
||
837 | |||
838 | if (ScaledContentWidth < this.viewport.Width) |
||
839 | { |
||
840 | this.ContentOffsetTransformX = (ContentViewportWidth - this.unScaledExtent.Width) / 2; |
||
841 | } |
||
842 | else |
||
843 | { |
||
844 | this.ContentOffsetTransformX = -this.ContentOffsetX; |
||
845 | } |
||
846 | |||
847 | this.ContentOffsetX = this.ContentOffsetX; |
||
848 | } |
||
849 | } |
||
850 | |||
851 | /// <summary> |
||
852 | /// Update the size of the viewport in content coordinates after the viewport size or 'ContentScale' has changed |
||
853 | /// </summary> |
||
854 | private void UpdateContentViewportSize() |
||
855 | { |
||
856 | ContentViewportWidth = viewport.Width / ContentScale; |
||
857 | ContentViewportHeight = viewport.Height / ContentScale; |
||
858 | |||
859 | ConstrainedContentViewportWidth = Math.Min(ContentViewportWidth, unScaledExtent.Width); |
||
860 | ConstrainedContentViewportHeight = Math.Min(ContentViewportHeight, unScaledExtent.Height); |
||
861 | |||
862 | UpdateTranslationX(); |
||
863 | UpdateTranslationY(); |
||
864 | } |
||
865 | |||
866 | /// <summary> |
||
867 | /// Update the viewport size from the specified size. (If viewport Changed Scale or Size) |
||
868 | /// </summary> |
||
869 | private void UpdateViewportSize(Size newSize) |
||
870 | { |
||
871 | //if (viewport == newSize) |
||
872 | //{ |
||
873 | // UpdateContentViewportSize(); |
||
874 | // return; |
||
875 | //} |
||
876 | |||
877 | viewport = newSize; |
||
878 | |||
879 | UpdateContentViewportSize(); |
||
880 | |||
881 | UpdateContentZoomFocusX(); |
||
882 | UpdateContentZoomFocusY(); |
||
883 | |||
884 | // |
||
885 | // Update content offset from itself when the size of the viewport changes. |
||
886 | // This ensures that the content offset remains properly clamped to its valid range. |
||
887 | // |
||
888 | this.ContentOffsetX = this.ContentOffsetX; |
||
889 | this.ContentOffsetY = this.ContentOffsetY; |
||
890 | |||
891 | if (scrollOwner != null) |
||
892 | { |
||
893 | scrollOwner.InvalidateScrollInfo(); |
||
894 | } |
||
895 | } |
||
896 | |||
897 | /// <summary> |
||
898 | /// Zoom in/out centered on the specified point (in content coordinates). |
||
899 | /// The focus point is kept locked to it's on sceen position (ala google maps). |
||
900 | /// </summary> |
||
901 | /// <param name="newContentScale">New Scale</param> |
||
902 | /// <param name="contentZoomFocus">Zoom Focuse Point</param> |
||
903 | public void ZoomAboutPoint(double newContentScale, Point contentZoomFocus) |
||
904 | { |
||
905 | newContentScale = Math.Min(Math.Max(newContentScale, MinContentScale), MaxContentScale); |
||
906 | |||
907 | if (contentOffsetTransform !=null) |
||
908 | { |
||
909 | var diffPointAndOffsetX = (contentZoomFocus.X + contentOffsetTransform.X); |
||
910 | var diffPointAndOffsetY = (contentZoomFocus.Y + contentOffsetTransform.Y); |
||
911 | |||
912 | ///Scale로 압축 |
||
913 | ///ScreenSpace 좌표 내에서 Offset 좌표를 뺀 후 현재 Scale 로 압축. |
||
914 | double screenSpaceZoomOffsetX = diffPointAndOffsetX * ContentScale; |
||
915 | double screenSpaceZoomOffsetY = diffPointAndOffsetY * ContentScale; |
||
916 | |||
917 | ///Scale로 늘림 |
||
918 | ///Scale로 압축된 좌표를 새로운 스케일로 늘린 contentSpaceZoom 좌표 |
||
919 | double contentSpaceZoomOffsetX = screenSpaceZoomOffsetX / newContentScale; |
||
920 | double contentSpaceZoomOffsetY = screenSpaceZoomOffsetY / newContentScale; |
||
921 | |||
922 | ///내가 원하는 좌표 포인트에 contentSpaceZoom 좌표를 뺀 다. |
||
923 | double newContentOffsetX = contentZoomFocus.X - contentSpaceZoomOffsetX; |
||
924 | double newContentOffsetY = contentZoomFocus.Y - contentSpaceZoomOffsetY; |
||
925 | |||
926 | this.ContentScale = newContentScale; |
||
927 | this.ContentOffsetX = newContentOffsetX; |
||
928 | this.ContentOffsetY = newContentOffsetY; |
||
929 | } |
||
930 | |||
931 | } |
||
932 | |||
933 | /// <summary> |
||
934 | /// ContentOffsetX |
||
935 | /// </summary> |
||
936 | private void UpdateContentZoomFocusX() |
||
937 | { |
||
938 | ContentZoomFocusX = ContentOffsetX + (ConstrainedContentViewportWidth / 2); |
||
939 | } |
||
940 | |||
941 | private void UpdateContentZoomFocusY() |
||
942 | { |
||
943 | ContentZoomFocusY = ContentOffsetY + (ConstrainedContentViewportHeight / 2); |
||
944 | } |
||
945 | |||
946 | public void AnimatedZoomTo(double contentScale) |
||
947 | { |
||
948 | Point zoomCenter = new Point(ContentOffsetX + (ContentViewportWidth / 2), |
||
949 | ContentOffsetY + (ContentViewportHeight / 2)); |
||
950 | |||
951 | AnimatedZoomAboutPoint(contentScale, zoomCenter); |
||
952 | } |
||
953 | |||
954 | /// <summary> |
||
955 | /// Use animation to center the view on the specified point (in content coordinates). |
||
956 | /// </summary> |
||
957 | public void AnimatedSnapTo(Point contentPoint) |
||
958 | { |
||
959 | double newX = contentPoint.X - (this.ContentViewportWidth / 2); |
||
960 | double newY = contentPoint.Y - (this.ContentViewportHeight / 2); |
||
961 | |||
962 | AnimationHelper.StartAnimation(this, ContentOffsetXProperty, newX, AnimationDuration); |
||
963 | AnimationHelper.StartAnimation(this, ContentOffsetYProperty, newY, AnimationDuration); |
||
964 | } |
||
965 | |||
966 | public void AnimatedZoomAboutPoint(double newContentScale, Point contentZoomFocus) |
||
967 | { |
||
968 | newContentScale = Math.Min(Math.Max(newContentScale, MinContentScale), MaxContentScale); |
||
969 | |||
970 | ContentZoomFocusX = contentZoomFocus.X; |
||
971 | ContentZoomFocusY = contentZoomFocus.Y; |
||
972 | |||
973 | enableContentOffsetUpdateFromScale = true; |
||
974 | |||
975 | AnimationHelper.StartAnimation(this, ContentScaleProperty, newContentScale, AnimationDuration, |
||
976 | delegate(object sender, EventArgs e) |
||
977 | { |
||
978 | enableContentOffsetUpdateFromScale = false; |
||
979 | }); |
||
980 | } |
||
981 | |||
982 | public void ScaleToFit() |
||
983 | { |
||
984 | if(content== null) |
||
985 | { |
||
986 | throw new ApplicationException("PART_Content was not found in the ZoomAndPanControl visual template!"); |
||
987 | } |
||
988 | |||
989 | ZoomTo(new Rect(0,0, content.ActualWidth, content.ActualHeight)); |
||
990 | } |
||
991 | |||
992 | |||
993 | public void Sync_ZoomTo(Rect contentRect) |
||
994 | { |
||
995 | double scaleX = this.ContentViewportWidth / contentRect.Width; |
||
996 | double scaleY = this.ContentViewportHeight / contentRect.Height; |
||
997 | double newScale = this.ContentScale * Math.Min(scaleX, scaleY); |
||
998 | |||
999 | newScale /= 2; |
||
1000 | |||
1001 | ZoomPointToViewportCenter(newScale, new Point(contentRect.X + |
||
1002 | (contentRect.Width), contentRect.Y + (contentRect.Height / 2))); |
||
1003 | } |
||
1004 | |||
1005 | public void ZoomTo(Rect contentRect) |
||
1006 | { |
||
1007 | double scaleX = this.ContentViewportWidth / contentRect.Width; |
||
1008 | double scaleY = this.ContentViewportHeight / contentRect.Height; |
||
1009 | double newScale = this.ContentScale * Math.Min(scaleX, scaleY); |
||
1010 | |||
1011 | ZoomPointToViewportCenter(newScale, new Point(contentRect.X + |
||
1012 | (contentRect.Width / 2), contentRect.Y + (contentRect.Height / 2))); |
||
1013 | } |
||
1014 | |||
1015 | public void ZoomPointToViewportCenter(double newContentScale, Point contentZoomFocus) |
||
1016 | { |
||
1017 | newContentScale = Math.Min(Math.Max(newContentScale, MinContentScale), MaxContentScale); |
||
1018 | this.ContentScale = newContentScale; |
||
1019 | this.ContentOffsetX = contentZoomFocus.X - (ContentViewportWidth / 2); |
||
1020 | this.ContentOffsetY = contentZoomFocus.Y - (ContentViewportHeight / 2); |
||
1021 | |||
1022 | } |
||
1023 | } |
||
1024 | } |