WPF疑难杂症会诊-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

WPF疑难杂症会诊

简介: 原文:WPF疑难杂症会诊       为什么图片像素是模糊的?   容器边框设为非整数时,其内容中的像素图片会产生模糊,即使设置SnapsToDevicePixels="True"也无效。
原文:WPF疑难杂症会诊

 

 

 

为什么图片像素是模糊的?

 

容器边框设为非整数时,其内容中的像素图片会产生模糊,即使设置SnapsToDevicePixels="True"也无效。

 

以下是范例代码:

 

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
<Page

  
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml">

  
<WrapPanel>  

  
<Border Margin="6" BorderThickness="1.5" BorderBrush="Red" HorizontalAlignment="Center" VerticalAlignment="Center">

    
<Image Width="146" Height="229" Stretch="None" Source="http://images.cnblogs.com/cnblogs_com/skyd/WPF_FLOWDOC_5.png%22/>

  </Border>

    <Border Margin="
6" BorderThickness="1" BorderBrush="Red" HorizontalAlignment="Center" VerticalAlignment="Center">

    
<Image Width="146" Height="229" Stretch="None"  Source="http://images.cnblogs.com/cnblogs_com/skyd/WPF_FLOWDOC_5.png%22/>

  </Border>

  </WrapPanel>

</Page>

 

 

我建立了两个Border,其边框宽度分别为1.51,内容都是载入的同一个Png格式图片。

 

效果如下:

 

可以看到,1.5像素边框的Border内的图片是模糊的,而另一个是正常的。

 

分析:

 

推断这种情况是因为受布局的位置影响,图片被绘制到非整数像素位置,这时为正确且无锯齿显示,就必须羽化自身像素。

 

解决的办法就是调整相关布局,避免在布局中使用非整数数值。

 

矢量图形不会受此影响。

 

 

Gird布局无法自适应内容扩展了!

 

在这篇文章里我曾写过如何自动化布局:http://www.cnblogs.com/SkyD/archive/2008/08/02/1258555.html

 

让窗口尺寸自适应内容,然后让Grid也自适应内容,这是一件很惬意的事。

 

比如这样:

 

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
<Window

  
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"

  SizeToContent
="WidthAndHeight">

  
<Grid Margin="6" Width="200" ShowGridLines="True">  

  
<Grid.RowDefinitions>

  
<RowDefinition Height="*"/>

  
<RowDefinition Height="*"/>

  
<RowDefinition Height="*"/>

  
</Grid.RowDefinitions>

  
<Ellipse Grid.Row="0" Height="55" Fill="Blue"/>

  
<Ellipse Grid.Row="1" Height="155" Fill="Red"/>

  
<Ellipse Grid.Row="2" Height="255" Fill="Green"/>

  
</Grid>

</Window>

 

效果为:

 

 

这看起来很完美,但是如果你把每个圆形的高度增加100像素,问题就出来了:

 

 

可以看到,最后一个圆形没有完全显示,这似乎很不合逻辑。

 

分析:

 

产生这种问题,应该是和屏幕分辨率有关的,我的屏幕分辨率是1440*900,我猜想窗口可能会自作聪明地将自己的尺寸限制在一个看起来比较舒适的的区域中,从而使得其内容被裁减。

 

你可以通过将“<RowDefinition Height="*"/>”全部改为“<RowDefinition Height="Auto"/>”来解决这一问题,设为“Auto”后,Grid将强制将行高定为其内容所需的高度。

 

此前我们所设置的“*”的作用实际上是为内容自动分配剩余空间的高度,在剩余空间充足的情况下,它还会自动进行一些智能化的调整,使得呈现更为合理,而当剩余空间紧张时,它就不得不强行为一些大块头开刀了。

 

设为“Auto”后的效果:

 

 

 

 

怎么才能禁止内容撑大容器?

 

这似乎很简单,只要设置容器为固定尺寸就可以了。但是假如我们为了自动化布局而不能设置容器尺寸呢?或者,假如我们在定义一个通用的样式,我们根本不知道目标容器的尺寸呢?

 

例如下面这段代码:

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
  <WrapPanel>  

    
<ListBox HorizontalContentAlignment="Stretch" Margin="8" Width="120" Height="220" BorderBrush="Blue" BorderThickness="3">

    
<ListBoxItem>1111111111</ListBoxItem>

    
<ListBoxItem>22222222222222222222</ListBoxItem>

    
<ListBoxItem>333333333333333333333333333333333333</ListBoxItem>

    
<ListBoxItem>44444444444444444444444444444444444444444</ListBoxItem>

    
</ListBox>

    
<ListBox HorizontalContentAlignment="Stretch" Margin="8" Width="180" Height="220" BorderBrush="Green" BorderThickness="3">

    
<ListBoxItem>1111111111</ListBoxItem>

    
<ListBoxItem>22222222222222222222</ListBoxItem>

    
<ListBoxItem>333333333333333333333333333333333333</ListBoxItem>

    
<ListBoxItem>44444444444444444444444444444444444444444</ListBoxItem>

    
</ListBox>

  
</WrapPanel>

 

有两个不同宽度的ListBox,我们设置其部分内容必定会超出其显示范围,现在的效果:

 

可以看到横向滚动条出现了。

 

我们定义一个模板来更直观的看一下显示效果:

 

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
<Style TargetType="ListBoxItem">

         
<Setter Property="Template">

            
<Setter.Value>

               
<ControlTemplate TargetType="ListBoxItem">

                  
<Border>

                     
<Border.Background>

                        
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">

                           
<GradientStop Offset="0" Color="OrangeRed"/>

                           
<GradientStop Offset="1" Color="Brown"/>

                        
</LinearGradientBrush>

                     
</Border.Background>

                     
<TextBlock Text="{TemplateBinding Content}"/>

                  
</Border>

               
</ControlTemplate>

            
</Setter.Value>

         
</Setter>

      
</Style>

 

 

我们将ListBoxItem的内容绑定给一个TextBlock,然后为其外围的Border设置了横向渐变背景。

 

显示为:

 

 

现在可以看到,渐变背景的长度参差不齐。

 

即使你为TextBlock设置换行,也不会有任何效果,它们仍然会撑开Border

 

分析:

 

子元素无法获知外围容器的尺寸,从而无法进行常规的控制,目前我只研究出一个偏方用于解决这一问题,就是在内容与外围容器之间加入一层Canvas

 

这个例子中就是为Border外加上Canvas,并把Border的背景挪给Canvas,完整代码如下:

 

 

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

   
<Page.Resources>

      
<Style TargetType="ListBoxItem">

         
<Setter Property="Template">

            
<Setter.Value>

               
<ControlTemplate TargetType="ListBoxItem">

                  
<Canvas Height="16">

                     
<Canvas.Background>

                        
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">

                           
<GradientStop Offset="0" Color="OrangeRed"/>

                           
<GradientStop Offset="1" Color="Brown"/>

                        
</LinearGradientBrush>

                     
</Canvas.Background>

                     
<Border>

                        
<TextBlock Text="{TemplateBinding Content}"/>

                     
</Border>

                  
</Canvas>

               
</ControlTemplate>

            
</Setter.Value>

         
</Setter>

      
</Style>

   
</Page.Resources>

   
<WrapPanel>

      
<ListBox

         
Width="120"

         Height
="220"

         Margin
="8"

         BorderBrush
="Blue"

         BorderThickness
="3"

         HorizontalContentAlignment
="Stretch">

         
<ListBoxItem>1111111111

         
</ListBoxItem>

         
<ListBoxItem>22222222222222222222

         
</ListBoxItem>

         
<ListBoxItem>333333333333333333333333333333333333

         
</ListBoxItem>

         
<ListBoxItem>44444444444444444444444444444444444444444

         
</ListBoxItem>

      
</ListBox>

      
<ListBox

         
Width="180"

         Height
="220"

         Margin
="8"

         BorderBrush
="Green"

         BorderThickness
="3"

         HorizontalContentAlignment
="Stretch">

         
<ListBoxItem>1111111111

         
</ListBoxItem>

         
<ListBoxItem>22222222222222222222

         
</ListBoxItem>

         
<ListBoxItem>333333333333333333333333333333333333

         
</ListBoxItem>

         
<ListBoxItem>44444444444444444444444444444444444444444

         
</ListBoxItem>

      
</ListBox>

   
</WrapPanel>

</Page>

 

 

显示效果:

 

 

可以看到,横向滚动条滚出去了,渐变背景也是整齐的了。

 

这个偏方并不完美,假如我们希望文字能够自动换行,或是在即将超出边框的地方显示省略号,都无法办到。

 

期待高手能提出更好的解决方案。

 

 

怎么弄出CheckListBox来?

 

 

 

见这篇文章:http://www.cnblogs.com/SkyD/archive/2008/07/23/1249950.html

 

 

如何在多选列表中实现右键菜单?

 

就是这种效果:

 

 

这看起来很简单,但是当你做的时候可能会比较头疼。

 

你会发现,当你用鼠标右键在上面单击的时候,右键菜单正确弹出,但是同时你鼠标所处位置的列表项的选取状态也被改变了,这时如果用户没有注意到,就可能会产生很严重的误操作,比如将本不该删除的项目删除了。

 

分析:

 

WPF中,鼠标右键也可进行选取列表项的操作,为了禁用这一功能,我们必须在ListBox中拦截下这个事件,让ListBoxItem无法获知此事件的发生。

 

这是由noorbakhsh提供的方法:

 

ListBox中加入事件处理:PreviewMouseRightButtonDown="listBox1_PreviewMouseRightButtonDown"

 

拦截事件:

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
        private void listBox1_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)

        {

            e.Handled 
= true;

        }

 

这样就成功的屏蔽了右键选取功能。

 

 

怎么让多选列表中所有项的选择状态反转?

 

这也是个看似简单的问题,但是在WPF是不同以往的情况。

 

首先这种方法是不行的:

 

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
ListBox lb = new ListBox();  

  

foreach (ListBoxItem lbi in lb.Items)  

{  

    lbi.IsSelected 
= !lbi.IsSelected;                 

}  

 

 

Items里装的不是ListBoxItem,而是源数据,比如stringFileInfo等等,任意类型。

 

需要使用这种方法设置:

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
        public static void ReSelect(ListBox l)   

        {   

            
for (int i = 0; i < l.Items.Count; i++)   

            {   

                var f 
= l.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;   

                f.IsSelected 
= !f.IsSelected;   

            }   

        } 

 

但是这还会引发一个问题,就是在列表内项目过多时,它会引发异常,而ListBox自带的ListBox.SelectAll() 和 ListBox.UnselectAll()方法则不会引发异常。

 

分析:

 

这种异常源自WPF的动态加载特性,这可以很大程度的节约载入时间和系统资源,但是如果你直接操作未加载到当前视图内的项,就会引发异常。

 

Marco Zhou 给出了两种解决方法:

 

1.禁用动态加载:<ListBox VirtualizingStackPanel.IsVirtualizing="False"/>

 

2.将列表项绑定到自定义类时,为自定义类增加一个用于控制是否选中的属性,并通过样式设定,将其绑定到列表项的IsSelected属性上:

 

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
<StackPanel>

  
<ListBox ItemsSource="{Binding}" x:Name="listBox" Width="200" Height="50">

    
<ListBox.ItemContainerStyle>

      
<Style TargetType="{x:Type ListBoxItem}">

        
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>

        
<Setter Property="Content" Value="{Binding Data}"/>

      
</Style>

    
</ListBox.ItemContainerStyle>

  
</ListBox>

  
<Button Margin="5" Width="120" Height="30" x:Name="button"/>

</StackPanel>

 

 

 

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifCode
public class DataObject : INotifyPropertyChanged

{

    
public DataObject(string data)

    {

        
this.data = data;

    }

 

    
private string data;

    
public string Data

    {

        
get { return data; }

        
set

        {

            data 
= value;

            NotifyChange(
"Data");

        }

    }

 

    
private Boolean isSelected = false;

    
public bool IsSelected

    {

        
get

        {

            
return isSelected;

        }

        
set

        {

            isSelected 
= value;

            NotifyChange(
"IsSelected");

        }

    }

 

    
private void NotifyChange(string propertyName)

    {

        
if (PropertyChanged != null)

        {

            PropertyChanged(
thisnew PropertyChangedEventArgs(propertyName));

        }

    }

 

    
public event PropertyChangedEventHandler PropertyChanged;

}

 

public partial class ListBoxDeselectDemo : Window

{

    
public ListBoxDeselectDemo()

    {

        InitializeComponent();

        listBox.SelectionMode 
= SelectionMode.Multiple;

        List
<DataObject> result = (from i in Enumerable.Range(120)

                                   select 
new DataObject("Item" + i.ToString())).ToList<DataObject>();

        
this.DataContext = result;

 

        button.Click 
+= delegate

        {

            
for (int i = 0; i < result.Count; i++)

            {

                result[i].IsSelected 
= !result[i].IsSelected;

            }

            listBox.Focus();

        };

    }

}

 

 

 

如何在多种样式之间共享相同的部分?

 

需要使用样式的继承属性,这是由RredCat提供的解决办法。

 

请参看这篇文章:http://www.cnblogs.com/SkyD/archive/2008/08/09/1264294.html

 

 

在此我列出了一些我在编写MailMail期间遇到的一些问题和解决办法,算是抛砖引玉,其中有些是使用偏方解决的,期待高手能提供出最佳的解决方法。

 

 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章