原文来自CodeProject,主要介绍如何创建自定义的Panel,如同Grid和StackPanel。
1) Introduction
文中介绍了两种Panel:FishPanel(鱼眼面板,点击时当前面板变大,其它面板变小,但整体宽度不变),FanPanel(帆面板,不知如何翻译比较贴切,就这样吧;子面板位置,大小都可以改变,同样整体宽度不变)。
2)Using the Code
最低版本VS2005,.Net 3.0;
3)Writing a custom panel
创建自定义的窗体,需要从System.Windows.Controls.Panel派生新类,然后重写:MeasureOverride和LayoutOverride。在计算阶段,调用者可以确认它需要多大的空间;也帮助我们确认子控件的大小;接下来就是把最终的大小参数传递到ArrangeOverride方法;每次我们队Layout有改变时,上面的两步操作会重复发生的。
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; }
在MeasureOverride中,让子控件来决定我们所需的空间大小,这是通过属性Child.DesiredSize来获得的;接下来就是调整比例大小,使其适应整个窗体。
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; }
在ArrangeOverride中,添加了比例计算,对每一个子控件都做变换,再统计总宽度;可以实现不同子控件变为相同宽度,或选择同样比例变成不同宽度,这是通过属性ScaleToFit来实现(代码中没看到?)。现在,我们只是把控件放在(0,0)处,接下来就是使用RenderTransoforms来移动控件。
鱼眼面板:设置3个子控件比其它的都大,所选哪3个控件是取决于当前鼠标的位置,其它的控件会填充余下的空间;
范面板:对子控件的旋转,缩放,平移来使控件以这三种方式排列:堆叠(stacked up),爆炸(exploded),包裹(wrap)
无论是鱼眼面板还是范面板,核心的处理过程都在AnimateAll(),可以查看它们的源代码。
4)Points of interest
无法在设计时直接以ItemControl方式显示,我们不能只指定一个名字就行,这是由于这两个控件只是一个模板,并不是真正的控件。作者想到了一个方法是:与Loaded事件相关联,然后把Sender转化为相关的面板模式。
为了演示界面效果,由于使用XPath和Binding不是很方便,作者使用了XmlDataProvider来加载所有图像。可在项目中先单独加载图像到一个Images文件夹,在TestData.xaml中我们看到:
<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>
在App.xaml中引用这些资源,使它们在整个项目中都可见。
<ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Resources/TestData.xaml" /> </ResourceDictionary.MergedDictionaries> <local:ImagePathConverter x:Key="ImagePathConverter" /> </ResourceDictionary>
5)FishEye Panel Demo
5.1)设置界面背景色:
<Window.Background> <ImageBrush ImageSource="Images/Azul.jpg" /> </Window.Background>
5.2)设置7行:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="2*"/> <RowDefinition Height="*"/> <RowDefinition Height="2*"/> <RowDefinition Height="*"/> <RowDefinition Height="2*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> </Grid>
5.3)设置第二行:
<Border CornerRadius="10" Background="#99ffffff" Grid.Row="1"> <ItemsControl DataContext="{Binding Source={StaticResource Things}}" ItemsSource="{Binding}" Margin="0"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <local:FishEyePanel Loaded="FishEye_Loaded" Magnification="3" AnimationMilliseconds="150" ScaleToFit="true"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="{x:Type ContentPresenter}"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <!-- This could obviously be any XAML. In our POC we dropped in entire UI pages and used it like a Vista taskbar --> <Image Source="{Binding Converter={StaticResource ImagePathConverter}, XPath=@Image}" Width="{Binding XPath=@Width}" Margin="5"/> </DataTemplate> </Setter.Value> </Setter> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl> </Border>
上面设置了FishEyePanel的Loaded事件,及Image Source。
5.4)在主窗体cs文件中添加Loaded事件处理过程:
void FishEye_Loaded(object sender, RoutedEventArgs e) { fish = (FishEyePanel)sender; }
5.5)演示界面(除去了后面两行的图像显示);点击其中的一张照片,左右的照片也一起变大,其余的照片不变;
实际上还有很多疑问,如不知如何改变图片的高度等。
附上FishEyePanel的源代码
5.6)FanPanel演示:默认状态
鼠标放在其上:
鼠标点击:
改变Detail Level值后的效果:
附上FanPanel的源代码。