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