markus / ZoomAndPan / ZoomAndPanControl.cs @ f87ee33e
이력 | 보기 | 이력해설 | 다운로드 (36.5 KB)
1 |
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 |
} |