프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

markus / KCOM / WrapPanel / WrapPanel.cs @ c4a8d205

이력 | 보기 | 이력해설 | 다운로드 (19 KB)

1
// (c) Copyright Microsoft Corporation.
2
// This source is subject to the Microsoft Public License (Ms-PL).
3
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
4
// All other rights reserved.
5

    
6
using System;
7
using System.ComponentModel;
8
using System.Diagnostics;
9
using System.Diagnostics.CodeAnalysis;
10
using System.Globalization;
11
using System.Windows;
12
using System.Windows.Controls;
13

    
14
namespace KCOM.WrapPanel
15
{
16
    /// <summary>
17
    /// Positions child elements sequentially from left to right or top to
18
    /// bottom.  When elements extend beyond the panel edge, elements are
19
    /// positioned in the next row or column.
20
    /// </summary>
21
    /// <QualityBand>Stable</QualityBand>
22
    public partial class WrapPanel : Panel
23
    {
24
        /// <summary>
25
        /// A value indicating whether a dependency property change handler
26
        /// should ignore the next change notification.  This is used to reset
27
        /// the value of properties without performing any of the actions in
28
        /// their change handlers.
29
        /// </summary>
30
        private bool _ignorePropertyChange;
31

    
32
        #region public double ItemHeight
33
        /// <summary>
34
        /// Gets or sets the height of the layout area for each item that is
35
        /// contained in a <see cref="T:System.Windows.Controls.WrapPanel" />.
36
        /// </summary>
37
        /// <value>
38
        /// The height applied to the layout area of each item that is contained
39
        /// within a <see cref="T:System.Windows.Controls.WrapPanel" />.  The
40
        /// default value is <see cref="F:System.Double.NaN" />.
41
        /// </value>
42
        [TypeConverter(typeof(LengthConverter))]
43
        public double ItemHeight
44
        {
45
            get { return (double)GetValue(ItemHeightProperty); }
46
            set { SetValue(ItemHeightProperty, value); }
47
        }
48

    
49
        /// <summary>
50
        /// Identifies the
51
        /// <see cref="P:System.Windows.Controls.WrapPanel.ItemHeight" />
52
        /// dependency property.
53
        /// </summary>
54
        /// <value>
55
        /// The identifier for the
56
        /// <see cref="P:System.Windows.Controls.WrapPanel.ItemHeight" />
57
        /// dependency property
58
        /// </value>
59
        public static readonly DependencyProperty ItemHeightProperty =
60
            DependencyProperty.Register(
61
                "ItemHeight",
62
                typeof(double),
63
                typeof(WrapPanel),
64
                new PropertyMetadata(double.NaN, OnItemHeightOrWidthPropertyChanged));
65
        #endregion public double ItemHeight
66

    
67
        #region public double ItemWidth
68
        /// <summary>
69
        /// Gets or sets the width of the layout area for each item that is
70
        /// contained in a <see cref="T:System.Windows.Controls.WrapPanel" />.
71
        /// </summary>
72
        /// <value>
73
        /// The width that applies to the layout area of each item that is
74
        /// contained in a <see cref="T:System.Windows.Controls.WrapPanel" />.
75
        /// The default value is <see cref="F:System.Double.NaN" />.
76
        /// </value>
77
        [TypeConverter(typeof(LengthConverter))]
78
        public double ItemWidth
79
        {
80
            get { return (double)GetValue(ItemWidthProperty); }
81
            set { SetValue(ItemWidthProperty, value); }
82
        }
83

    
84
        /// <summary>
85
        /// Identifies the
86
        /// <see cref="P:System.Windows.Controls.WrapPanel.ItemWidth" />
87
        /// dependency property.
88
        /// </summary>
89
        /// <value>
90
        /// The identifier for the
91
        /// <see cref="P:System.Windows.Controls.WrapPanel.ItemWidth" />
92
        /// dependency property.
93
        /// </value>
94
        public static readonly DependencyProperty ItemWidthProperty =
95
            DependencyProperty.Register(
96
                "ItemWidth",
97
                typeof(double),
98
                typeof(WrapPanel),
99
                new PropertyMetadata(double.NaN, OnItemHeightOrWidthPropertyChanged));
100
        #endregion public double ItemWidth
101

    
102
        #region public Orientation Orientation
103
        /// <summary>
104
        /// Gets or sets the direction in which child elements are arranged.
105
        /// </summary>
106
        /// <value>
107
        /// One of the <see cref="T:System.Windows.Controls.Orientation" />
108
        /// values.  The default is
109
        /// <see cref="F:System.Windows.Controls.Orientation.Horizontal" />.
110
        /// </value>
111
        public Orientation Orientation
112
        {
113
            get { return (Orientation)GetValue(OrientationProperty); }
114
            set { SetValue(OrientationProperty, value); }
115
        }
116

    
117
        /// <summary>
118
        /// Identifies the
119
        /// <see cref="P:System.Windows.Controls.WrapPanel.Orientation" />
120
        /// dependency property.
121
        /// </summary>
122
        /// <value>
123
        /// The identifier for the
124
        /// <see cref="P:System.Windows.Controls.WrapPanel.Orientation" />
125
        /// dependency property.
126
        /// </value>
127
        // TODO: In WPF, WrapPanel uses AddOwner to register the Orientation
128
        // property via StackPanel.  It then gets the default value of
129
        // StackPanel's Orientation property.  It looks like this should be no
130
        // different than using the same default value on a new Orientation
131
        // property.
132
        public static readonly DependencyProperty OrientationProperty =
133
            DependencyProperty.Register(
134
                "Orientation",
135
                typeof(Orientation),
136
                typeof(WrapPanel),
137
                new PropertyMetadata(Orientation.Horizontal, OnOrientationPropertyChanged));
138

    
139
        /// <summary>
140
        /// OrientationProperty property changed handler.
141
        /// </summary>
142
        /// <param name="d">WrapPanel that changed its Orientation.</param>
143
        /// <param name="e">Event arguments.</param>
144
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Almost always set from the CLR property.")]
145
        private static void OnOrientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
146
        {
147
            WrapPanel source = (WrapPanel)d;
148
            Orientation value = (Orientation)e.NewValue;
149

    
150
            // Ignore the change if requested
151
            if (source._ignorePropertyChange)
152
            {
153
                source._ignorePropertyChange = false;
154
                return;
155
            }
156

    
157
            // Validate the Orientation
158
            if ((value != Orientation.Horizontal) &&
159
                (value != Orientation.Vertical))
160
            {
161
                // Reset the property to its original state before throwing
162
                source._ignorePropertyChange = true;
163
                source.SetValue(OrientationProperty, (Orientation)e.OldValue);
164

    
165
                string message = string.Format(
166
                    CultureInfo.InvariantCulture,
167
                    "thrown when the Orientation property is provided an invalid value.",
168
                    value);
169
                throw new ArgumentException(message, "value");
170
            }
171

    
172
            // Orientation affects measuring.
173
            source.InvalidateMeasure();
174
        }
175
        #endregion public Orientation Orientation
176

    
177
        /// <summary>
178
        /// Initializes a new instance of the
179
        /// <see cref="T:System.Windows.Controls.WrapPanel" /> class.
180
        /// </summary>
181
        public WrapPanel()
182
        {
183
        }
184

    
185
        /// <summary>
186
        /// Property changed handler for ItemHeight and ItemWidth.
187
        /// </summary>
188
        /// <param name="d">
189
        /// WrapPanel that changed its ItemHeight or ItemWidth.
190
        /// </param>
191
        /// <param name="e">Event arguments.</param>
192
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Almost always set from the CLR property.")]
193
        private static void OnItemHeightOrWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
194
        {
195
            WrapPanel source = (WrapPanel)d;
196
            double value = (double)e.NewValue;
197

    
198
            // Ignore the change if requested
199
            if (source._ignorePropertyChange)
200
            {
201
                source._ignorePropertyChange = false;
202
                return;
203
            }
204

    
205
            // Validate the length (which must either be NaN or a positive,
206
            // finite number)
207
            if (!double.IsNaN(value) && ((value <= 0.0) || double.IsPositiveInfinity(value)))
208
            {
209
                // Reset the property to its original state before throwing
210
                source._ignorePropertyChange = true;
211
                source.SetValue(e.Property, (double)e.OldValue);
212

    
213
                string message = string.Format(
214
                    CultureInfo.InvariantCulture,
215
                    "thrown when the ItemWith or ItemHeight properties are provided an invalid value",
216
                    value);
217
                throw new ArgumentException(message, "value");
218
            }
219

    
220
            // The length properties affect measuring.
221
            source.InvalidateMeasure();
222
        }
223

    
224
        /// <summary>
225
        /// Measures the child elements of a
226
        /// <see cref="T:System.Windows.Controls.WrapPanel" /> in anticipation
227
        /// of arranging them during the
228
        /// <see cref="M:System.Windows.Controls.WrapPanel.ArrangeOverride(System.Windows.Size)" />
229
        /// pass.
230
        /// </summary>
231
        /// <param name="constraint">
232
        /// The size available to child elements of the wrap panel.
233
        /// </param>
234
        /// <returns>
235
        /// The size required by the
236
        /// <see cref="T:System.Windows.Controls.WrapPanel" /> and its 
237
        /// elements.
238
        /// </returns>
239
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")]
240
        protected override Size MeasureOverride(Size constraint)
241
        {
242
            // Variables tracking the size of the current line, the total size
243
            // measured so far, and the maximum size available to fill.  Note
244
            // that the line might represent a row or a column depending on the
245
            // orientation.
246
            Orientation o = Orientation;
247
            OrientedSize lineSize = new OrientedSize(o);
248
            OrientedSize totalSize = new OrientedSize(o);
249
            OrientedSize maximumSize = new OrientedSize(o, constraint.Width, constraint.Height);
250

    
251
            // Determine the constraints for individual items
252
            double itemWidth = ItemWidth;
253
            double itemHeight = ItemHeight;
254
            bool hasFixedWidth = !double.IsNaN(itemWidth);
255
            bool hasFixedHeight = !double.IsNaN(itemHeight);
256
            Size itemSize = new Size(
257
                hasFixedWidth ? itemWidth : constraint.Width,
258
                hasFixedHeight ? itemHeight : constraint.Height);
259

    
260
            // Measure each of the Children
261
            foreach (UIElement element in Children)
262
            {
263
                // Determine the size of the element
264
                element.Measure(itemSize);
265
                OrientedSize elementSize = new OrientedSize(
266
                    o,
267
                    hasFixedWidth ? itemWidth : element.DesiredSize.Width,
268
                    hasFixedHeight ? itemHeight : element.DesiredSize.Height);
269

    
270
                // If this element falls of the edge of the line
271
                if (NumericExtensions.IsGreaterThan(lineSize.Direct + elementSize.Direct, maximumSize.Direct))
272
                {
273
                    // Update the total size with the direct and indirect growth
274
                    // for the current line
275
                    totalSize.Direct = Math.Max(lineSize.Direct, totalSize.Direct);
276
                    totalSize.Indirect += lineSize.Indirect;
277

    
278
                    // Move the element to a new line
279
                    lineSize = elementSize;
280

    
281
                    // If the current element is larger than the maximum size,
282
                    // place it on a line by itself
283
                    if (NumericExtensions.IsGreaterThan(elementSize.Direct, maximumSize.Direct))
284
                    {
285
                        // Update the total size for the line occupied by this
286
                        // single element
287
                        totalSize.Direct = Math.Max(elementSize.Direct, totalSize.Direct);
288
                        totalSize.Indirect += elementSize.Indirect;
289

    
290
                        // Move to a new line
291
                        lineSize = new OrientedSize(o);
292
                    }
293
                }
294
                else
295
                {
296
                    // Otherwise just add the element to the end of the line
297
                    lineSize.Direct += elementSize.Direct;
298
                    lineSize.Indirect = Math.Max(lineSize.Indirect, elementSize.Indirect);
299
                }
300
            }
301

    
302
            // Update the total size with the elements on the last line
303
            totalSize.Direct = Math.Max(lineSize.Direct, totalSize.Direct);
304
            totalSize.Indirect += lineSize.Indirect;
305

    
306
            // Return the total size required as an un-oriented quantity
307
            return new Size(totalSize.Width, totalSize.Height);
308
        }
309

    
310
        /// <summary>
311
        /// Arranges and sizes the
312
        /// <see cref="T:System.Windows.Controls.WrapPanel" /> control and its
313
        /// child elements.
314
        /// </summary>
315
        /// <param name="finalSize">
316
        /// The area within the parent that the
317
        /// <see cref="T:System.Windows.Controls.WrapPanel" /> should use 
318
        /// arrange itself and its children.
319
        /// </param>
320
        /// <returns>
321
        /// The actual size used by the
322
        /// <see cref="T:System.Windows.Controls.WrapPanel" />.
323
        /// </returns>
324
        protected override Size ArrangeOverride(Size finalSize)
325
        {
326
            // Variables tracking the size of the current line, and the maximum
327
            // size available to fill.  Note that the line might represent a row
328
            // or a column depending on the orientation.
329
            Orientation o = Orientation;
330
            OrientedSize lineSize = new OrientedSize(o);
331
            OrientedSize maximumSize = new OrientedSize(o, finalSize.Width, finalSize.Height);
332

    
333
            // Determine the constraints for individual items
334
            double itemWidth = ItemWidth;
335
            double itemHeight = ItemHeight;
336
            bool hasFixedWidth = !itemWidth.IsNaN();
337
            bool hasFixedHeight = !itemHeight.IsNaN();
338
            double indirectOffset = 0;
339
            double? directDelta = (o == Orientation.Horizontal) ?
340
                (hasFixedWidth ? (double?)itemWidth : null) :
341
                (hasFixedHeight ? (double?)itemHeight : null);
342

    
343
            // Measure each of the Children.  We will process the elements one
344
            // line at a time, just like during measure, but we will wait until
345
            // we've completed an entire line of elements before arranging them.
346
            // The lineStart and lineEnd variables track the size of the
347
            // currently arranged line.
348
            UIElementCollection children = Children;
349
            int count = children.Count;
350
            int lineStart = 0;
351
            for (int lineEnd = 0; lineEnd < count; lineEnd++)
352
            {
353
                UIElement element = children[lineEnd];
354

    
355
                // Get the size of the element
356
                OrientedSize elementSize = new OrientedSize(
357
                    o,
358
                    hasFixedWidth ? itemWidth : element.DesiredSize.Width,
359
                    hasFixedHeight ? itemHeight : element.DesiredSize.Height);
360

    
361
                // If this element falls of the edge of the line
362
                if (NumericExtensions.IsGreaterThan(lineSize.Direct + elementSize.Direct, maximumSize.Direct))
363
                {
364
                    // Then we just completed a line and we should arrange it
365
                    ArrangeLine(lineStart, lineEnd, directDelta, indirectOffset, lineSize.Indirect);
366

    
367
                    // Move the current element to a new line
368
                    indirectOffset += lineSize.Indirect;
369
                    lineSize = elementSize;
370

    
371
                    // If the current element is larger than the maximum size
372
                    if (NumericExtensions.IsGreaterThan(elementSize.Direct, maximumSize.Direct))
373
                    {
374
                        // Arrange the element as a single line
375
                        ArrangeLine(lineEnd, ++lineEnd, directDelta, indirectOffset, elementSize.Indirect);
376

    
377
                        // Move to a new line
378
                        indirectOffset += lineSize.Indirect;
379
                        lineSize = new OrientedSize(o);
380
                    }
381

    
382
                    // Advance the start index to a new line after arranging
383
                    lineStart = lineEnd;
384
                }
385
                else
386
                {
387
                    // Otherwise just add the element to the end of the line
388
                    lineSize.Direct += elementSize.Direct;
389
                    lineSize.Indirect = Math.Max(lineSize.Indirect, elementSize.Indirect);
390
                }
391
            }
392

    
393
            // Arrange any elements on the last line
394
            if (lineStart < count)
395
            {
396
                ArrangeLine(lineStart, count, directDelta, indirectOffset, lineSize.Indirect);
397
            }
398

    
399
            return finalSize;
400
        }
401

    
402
        /// <summary>
403
        /// Arrange a sequence of elements in a single line.
404
        /// </summary>
405
        /// <param name="lineStart">
406
        /// Index of the first element in the sequence to arrange.
407
        /// </param>
408
        /// <param name="lineEnd">
409
        /// Index of the last element in the sequence to arrange.
410
        /// </param>
411
        /// <param name="directDelta">
412
        /// Optional fixed growth in the primary direction.
413
        /// </param>
414
        /// <param name="indirectOffset">
415
        /// Offset of the line in the indirect direction.
416
        /// </param>
417
        /// <param name="indirectGrowth">
418
        /// Shared indirect growth of the elements on this line.
419
        /// </param>
420
        private void ArrangeLine(int lineStart, int lineEnd, double? directDelta, double indirectOffset, double indirectGrowth)
421
        {
422
            double directOffset = 0.0;
423

    
424
            Orientation o = Orientation;
425
            bool isHorizontal = o == Orientation.Horizontal;
426

    
427
            UIElementCollection children = Children;
428
            for (int index = lineStart; index < lineEnd; index++)
429
            {
430
                // Get the size of the element
431
                UIElement element = children[index];
432
                OrientedSize elementSize = new OrientedSize(o, element.DesiredSize.Width, element.DesiredSize.Height);
433

    
434
                // Determine if we should use the element's desired size or the
435
                // fixed item width or height
436
                double directGrowth = directDelta != null ?
437
                    directDelta.Value :
438
                    elementSize.Direct;
439

    
440
                // Arrange the element
441
                Rect bounds = isHorizontal ?
442
                    new Rect(directOffset, indirectOffset, directGrowth, indirectGrowth) :
443
                    new Rect(indirectOffset, directOffset, indirectGrowth, directGrowth);
444
                element.Arrange(bounds);
445

    
446
                directOffset += directGrowth;
447
            }
448
        }
449
    }
450
}
클립보드 이미지 추가 (최대 크기: 500 MB)