|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionRecently, I needed to write some custom panels (like There're two different panels - The The Now just because I work for Microsoft doesn't mean that these samples are correct. I'm not in the product groups or anything, and don't have any insider information. I came up with the strategy of arranging the children on top of one another at (0,0) and then using Using the codeTo compile the code, you need VS2005, .NET Framework 3.0 (RC1), and the Visual Studio "Orcas" extensions. If you don't have the extensions, VS won't recognise the project type. I've included pre-built EXEs so you can play with just the RC1 .NET Framework. The code should just require re-compiling for later versions of the framework. Writing a custom panelTo get your own custom panel off the ground, you need to derive from protected override Size MeasureOverride(Size availableSize)
{
Size idealSize = new Size(0, 0);
// Allow children as much room as they want - then scale them
Size size = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
foreach (UIElement child in Children)
{
child.Measure(size);
idealSize.Width += child.DesiredSize.Width;
idealSize.Height = Math.Max(idealSize.Height,
child.DesiredSize.Height);
}
// EID calls us with infinity, but framework
// doesn't like us to return infinity
if (double.IsInfinity(availableSize.Height) ||
double.IsInfinity(availableSize.Width))
return idealSize;
else
return availableSize;
}
In our protected override Size ArrangeOverride(Size finalSize)
{
if (this.Children == null || this.Children.Count == 0)
return finalSize;
ourSize = finalSize;
totalWidth = 0;
foreach (UIElement child in this.Children)
{
// If this is the first time
// we've seen this child, add our transforms
if (child.RenderTransform as TransformGroup == null)
{
child.RenderTransformOrigin = new Point(0, 0.5);
TransformGroup group = new TransformGroup();
child.RenderTransform = group;
group.Children.Add(new ScaleTransform());
group.Children.Add(new TranslateTransform());
// group.Children.Add(new RotateTransform());
}
child.Arrange(new Rect(0, 0, child.DesiredSize.Width,
child.DesiredSize.Height));
totalWidth += child.DesiredSize.Width;
}
AnimateAll();
return finalSize;
}
In our The heart of the // These next few lines took two of us hours to write!
double mag = Magnification;
double extra = 0;
if (theChild != null)
extra += mag - 1;
if (prevChild == null)
extra += ratio * (mag - 1);
else if (nextChild == null)
extra += ((mag - 1 ) * (1 - ratio));
else
extra += mag - 1;
double prevScale = this.Children.Count * (1 + ((mag - 1) *
(1 - ratio))) / (this.Children.Count + extra);
double theScale = (mag * this.Children.Count) /
(this.Children.Count + extra);
double nextScale = this.Children.Count * (1 + ((mag - 1) * ratio)) /
(this.Children.Count + extra);
double otherScale = this.Children.Count /
(this.Children.Count + extra);
// Applied to all non-interesting children
This is some of the hardest code I've ever written, and took about half a day with two of us with pens and paper to figure out! I'm not going to explain the math - suffice it to say that it scales three children to be larger than the others, dependant on where the mouse is, and resizes all the others to take up the remaining space. The heart of the if (!IsWrapPanel)
{
if (!this.IsMouseOver)
{
// Rotate all the children into a stack
double r = 0;
int sign = +1;
foreach (UIElement child in this.Children)
{
if (foundNewChildren)
child.SetValue(Panel.ZIndexProperty, 0);
AnimateTo(child, r, 0, 0, scaleFactor);
r += sign * 15; // +-15 degree intervals
if (Math.Abs(r) > 90)
{
r = 0;
sign = -sign;
}
}
}
else
{
// On mouse over explode out the children and don't rotate them
Random rand = new Random();
foreach (UIElement child in this.Children)
{
child.SetValue(Panel.ZIndexProperty,
rand.Next(this.Children.Count));
double x = (rand.Next(16) - 8) * ourSize.Width / 32;
double y = (rand.Next(16) - 8) * ourSize.Height / 32;
AnimateTo(child, 0, x, y, scaleFactor);
}
}
}
else
{
// Pretend to be a wrap panel
double maxHeight = 0, x = 0, y = 0;
foreach (UIElement child in this.Children)
{
if (child.DesiredSize.Height > maxHeight)
// Row height
maxHeight = child.DesiredSize.Height;
if (x + child.DesiredSize.Width > this.ourSize.Width)
{
x = 0;
y += maxHeight;
}
if (y > this.ourSize.Height - maxHeight)
child.Visibility = Visibility.Hidden;
else
child.Visibility = Visibility.Visible;
AnimateTo(child, 0, x, y, 1);
x += child.DesiredSize.Width;
}
}
Again, this scales/rotates/transforms all the children into their three possible arrangements: stacked up, exploded, or wrap panel style. Note that we animate between the different states so adding children looks pretty funky too (not shown in demo). The Points of interestThere are a few neat things which took a while to figure out, and are used in both samples. If you need to get a reference to the panel used in an The way I do the test data is pretty cool too. Martin Grayson came up with this method - take a look at TestData.xaml. It uses an <XmlDataProvider x:Key="Things" XPath="Things/Thing">
<x:XData>
<Things xmlns="">
<Thing Image="Aquarium.jpg"/>
<Thing Image="Ascent.jpg"/>
<Thing Image="Autumn.jpg"/>
<Thing Image="Crystal.jpg"/>
<Thing Image="DaVinci.jpg"/>
<Thing Image="Follow.jpg"/>
<Thing Image="Friend.jpg"/>
<Thing Image="Home.jpg"/>
<Thing Image="Moon flower.jpg"/>
</Things>
</x:XData>
</XmlDataProvider>
Also note how the resources are referenced in the App.xaml so they are globally available. Another really wonderful property is setting Another cool thing is the public class ImagePathConverter : IValueConverter
{
#region IValueConverter Members
private static string path;
public object Convert(object value, Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
if (path == null)
{
path = Path.GetDirectoryName(Path.GetDirectoryName(
Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location))) +
"\\Images";
if (!Directory.Exists(path))
{
path = Path.GetDirectoryName(
Assembly.GetExecutingAssembly().Location) +
"\\Images";
if (!Directory.Exists(path))
throw new FileNotFoundException("Can't " +
"find images folder", path);
}
path += "\\";
}
return string.Format("{0}{1}", path, (string)value);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
One last point to note is, in the SummaryImplementing your own panels can be a lot of fun, but the layout code can be really hard to write. You need to solve simultaneous equations and the like. Please use this code as you like. You can even include it in a product which you sell if you want. I'm goaled on driving WPF adoption, which is why I publish sample code. HistoryFixed to handle different sized children - added
|
||||||||||||||||||||||