代码改变世界

wpf开发常见问题(1)

2008-10-22 21:42  Clingingboy  阅读(4493)  评论(5编辑  收藏  举报

      经过一段时间wpf的学习和实际开发.现在与大家分享下,在实际中wpf遇到的一些实际问题.silverlight 2.0正式版已经出来.sliverlight的功能应该与wpf大步分类似。其中的经验照样可以套用到sliverlight上.现在开始.

 

一.与模板相关问题

1.如何取得模板中的元素?

直切重点

(1)第一步确定控件相关ContentPresenter.给出一个扩展方法

public static childItem FindVisualChild<childItem>(this DependencyObject obj)
where childItem : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        
        if (child != null && child is childItem)
            return (childItem)child;
        else
        {
            childItem childOfChild = FindVisualChild<childItem>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}
(2)通过ContentControl控件的ContentTemplate属性的FindName方法获取元素
public static object FindTemplateChild(this ContentControl obj, string name)
{
    ContentPresenter content = obj.FindVisualChild<ContentPresenter>();
    object child = obj.ContentTemplate.FindName(name, content);
    return child;
}
进一步用泛型封装
public static childItem FindTemplateChild<childItem>(this ContentControl obj, string name)
    where childItem : DependencyObject
{

    ContentPresenter content = obj.FindVisualChild<ContentPresenter>();
    object child = obj.ContentTemplate.FindName(name, content);
    if (child != null && child is childItem)
    return (childItem)child;

    return null;
}
在实际情况中,我们还可能用到winfroms控件,所以再来一个
public static childItem FindFormsTemplateChild<childItem>(this ContentControl obj, string name, FrameworkElement parent)
    where childItem : System.Windows.Forms.Control
{
    object child = obj.ContentTemplate.FindName(name, parent);
    if (child != null && child is childItem)
        return (childItem)child;
   
    return null;
}

2.如果获取元素父级元素?

public static childItem FindAncestor<childItem>(this Visual visual)
    where childItem : Visual
{
    while (visual != null && !typeof(childItem).IsInstanceOfType(visual))
    {
        visual = (Visual)VisualTreeHelper.GetParent(visual);
        
    }
    if (visual != null && visual is childItem)
        return (childItem)visual;

    return null;
}

3.何时可以取到模板的元素?

在你需要取模板元素的时候,必须保证模板已经加载了.那如何才能保证模板已经加载?

3-1确保控件(元素)在页面上出现.这个讲法比较通俗,意思就是要保证模板已经加载完成.给出一个简单的例子(目的,用Tab1的button事件去取得Tab2的ListBox控件的模板)

<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="demo">
            <Label x:Name="labelCrl" Background="Red" Content="{Binding}"></Label>
        </DataTemplate>
    </Grid.Resources>
    <TabControl>
        <TabItem Header="Button">
            <Button Height="23" Margin="143,0,82,81" Name="button1" VerticalAlignment="Bottom" Click="button1_Click">Button</Button>
        </TabItem>
        <TabItem Header="ListBox">
            <StackPanel>
                <ListBox x:Name="boxDemo" ItemTemplate="{StaticResource demo}"></ListBox>
                <ListBox Margin="10,0,0,5" Name="lb">
                    <ListBoxItem>Item 0</ListBoxItem>
                </ListBox>
            </StackPanel>
           
        </TabItem>
    </TabControl>
</Grid>

后端,以下代码是无效,除非第一次进来已经切换过Tab2,模板加载完成后才有效.

private void button1_Click(object sender, RoutedEventArgs e)
{
    //if in different tabitem,I can't fetch the ListBoxItem
    ListBoxItem obj = boxDemo.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem;
    ContentPresenter obj2 = boxDemo.FindVisualChild<ContentPresenter>();
    Object label = obj.ContentTemplate.FindName("labelCrl", obj2);
}

那如何解决这个问题?

(1)利用数据绑定的功能,只更改相关属性,模板则自动更新

(2)不要在button事件中更改ui,定义出你需要更改的属性,然后在控件的Onload事件中更改.如你需要更改Label的BackGround,你可以定义一个属性,然后把上面这段代码提取到ListBox的Onload事件当中.

3-2 利用Dispatcher属性异步等待模板加载完成

在实际开发中,有可能会遇到这种现象,取模板的元素,有时成功有时失败,不同机器配置取模板的速度都会不同. 在未理解Dispatcher的使用之前,为防止模板未加载完成.我利用System.Timers.Timer 来等待模板加载完成.

如下示例,使用一个Timer对象反选一个列表中模板中的元素,如果没有取到对象的话,则重新执行Timer事件(Start方法).因为不在同一线程,所以还是需要调用Dispatcher的BeginInvoke方法,进行异步操作.

private Timer timer;
       
public ItemListView()
{
    timer = new Timer();
    timer.Interval = 100;
    timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
}

void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    timer.Stop();
    Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
        new Action(this.UnCheckAll));
}

public void UnCheckAll()
{

    if (this.SelectedItem != null)
    {
        foreach (var item in this.ItemsSource)
        {
            DependencyObject viewItem = this.ItemContainerGenerator.ContainerFromItem(item);
            if (item != this.SelectedItem)
            {

                if (viewItem != null)
                {
                    CheckBox box = viewItem.FindVisualChild<CheckBox>();
                    if (box != null)
                    {
                        box.IsChecked = false;
                    }
                    else
                    {
                        timer.Start();
                    }
                }
            }
        }
    }
}

以上的做法虽然可以解决问题,但并非最好办法.勉强可用.

设置异步请求的优先级别即DispatcherPriority.看一下以下的说明,默认一般都设置为Normal,我们可以设置为Background,这样就万无一失了

Capture

 

现在以上的代码变为

public ItemListView()
{
   this.Loaded+=new RoutedEventHandler(ItemListView_Loaded);
}

void ItemListView_Loaded(object sender, RoutedEventArgs e)
{
    Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background,
         new Action(this.UnCheckAll));
}

这一点很重要,取模板之前最好用Dispatcher的BeginInvoke方法去取模板元素,这样不会出错.

4.如何在后端定义一个默认模板?

在定义模板选择器的时候,如果用户未自定义模板的话,可以简单的在后端定义一个模板,方法如下,此方法并不推荐使用了.

private DataTemplate CreateDefault()
{
    DataTemplate tpl = new DataTemplate();
    FrameworkElementFactory box = new FrameworkElementFactory(typeof(CheckBox));
    Binding bind = new Binding();
    box.SetBinding(CheckBox.TagProperty, bind);
    box.AddHandler(CheckBox.ClickEvent, new RoutedEventHandler(selectChanged));
    tpl.VisualTree = box;
    return tpl;
}

其他方面就看msdn和相关书籍吧,以上方法已经很实用,能解决大部分问题了.

 

二.数据绑定相关

1.赋值不等于绑定

this.DataContext = this.ViewModel;

一般情况下,一个DataContext是通过赋值来执行的.与其绑定的数据来源均实现INotifyPropertyChanged,以实现双向绑定通知

然后在其DataContext 范围内的元素均能共享这个数据源,进行绑定

this.announceContent.SetBinding(AnnounceDetailView.BackgroundProperty, new Binding("xxx"));

这个时候再进行属性的更改,绑定的属性将会改变.

this.announceContent.Background = Brushes.Red;

当进行主从绑定的时候,很容易看到DataContext属性就毫不犹豫的进行赋值操作,这时应该进行绑定操作,不要做了赋值的操作,还在为自己狡辩,我真的绑定了怎么没出效果:)

2.只有依赖属性才可以绑定

数据绑定的类型转换器非常的常用,动不动就需要,有时候你需要传入一个参数,而且是依赖属性。可以参考我这里以前写的一篇

http://www.clingingboy.cn/index.php/archives/36

3.如何用绑定语法绑定整个对象

如果要绑定初始对象,可以这样写.加Path=.跟不加有区别的哦

{Binding Path=.}

4.绑定到字典

顺便把以前写的拿过来

{Binding dd[xxx]}

5.绑定选择对象

(1)先设置SelectedValuePath

(2)绑定SelectedItem

(3)绑定SelectedValue

如果在xmal中以上属性设置无效,需要在后端设置,因为无法判断先设置哪个属性即Selector控件需要选择一个默认的对象.

否则的话可以尝试绑定后更改绑定的对象.

6.绑定的内存泄露问题

看这个http://support.microsoft.com/kb/938416

7.与绑定相关的验证问题

我认为wpf内置的验证还不够方面,在.net 3.5sp1对验证功能进行了一些改善.

这个问题将单独拿出来讨论,我将在这篇随笔里面进行补充,提供给大家一种解决方案,供大家参考。

http://www.cnblogs.com/Clingingboy/archive/2008/08/24/1274934.html

8.绑定数据,但要注意性能.

为了实现绑定,有时会从数据库里面把绑定的数据一次性全部取出来,这样没必要,绑定很好,但也要注意性能.

另外其他的相关数据绑定的可以看msdn,很详细,关键还是多练.

三.资源文件相关

1.编译后的资源文件加载速度更快.

默认wpf提供了多种皮肤,而其是以dll的方式提供了,如PresentationFramework.Luna,一个xp下默认的皮肤.在开发过程中(vista)为了统一界面风格,我们需要加载xp皮肤.

Uri url = new Uri("PresentationFramework.Luna;V3.0.0.0;31bf3856ad364e35;component/themes/luna.normalcolor.xaml", UriKind.Relative);
System.Windows.ResourceDictionary resource = (System.Windows.ResourceDictionary)System.Windows.Application.LoadComponent(url);
System.Windows.Application.Current.Resources.MergedDictionaries.Add(resource);

同样当我们在开发后期我们应该把我们的资源文件整理到一个项目里面进行编译后进行加载,这样性能优于把资源放在每个相关的xaml中.wpf版本的雅虎通就是这么做的:).

这篇文章可以进行比较.很明显的,推荐看一下.

http://www.codeproject.com/KB/WPF/wpfskins.aspx

2.给资源合理分类

当然是为了更好的管理资源文件了,可以参考下此文.

http://www.paulstovell.com/blog/xaml-and-wpf-coding-guidelines

还同时给出了如何更好的写xaml的方案.

先写到这里,有时间再写第二篇,给大家一个参考

public ReferencedAssemblySkin(string name, Uri resourceUri) : base(name)
{
    this._resourceUri = resourceUri;
}