标签归档:wpf

WPF TextBox输入显示提示

在网上支付输入银行卡的时候,经常看到输入的数字会放大和提示。

下面是WPF版的一个例子。

 public class ZoomTextTooltip : FrameworkElement
    {
        public object ZoomText
        {
            get { return (object)GetValue(ZoomTextProperty); }
            set { SetValue(ZoomTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomTextProperty =
            DependencyProperty.Register("ZoomText", typeof(object), typeof(ZoomTextTooltip), new UIPropertyMetadata(null,
                (o, args) =>
                {
                    var textBox = args.NewValue as TextBox;
                    if (textBox == null)
                        return;
                    var zoomTextTooltip = o as ZoomTextTooltip;
                    if (zoomTextTooltip == null)
                        return;
                    var popup = new Popup();
                    var textBlock = new TextBlock
                    {
                        Text = textBox.Text,
                        FontWeight = FontWeights.Bold,
                        FontSize = zoomTextTooltip.CustomFontSize,
                        Background = zoomTextTooltip.CustomBackground,
                        Foreground = zoomTextTooltip.CustomForeground
                    };
                    var binding = new Binding();
                    binding.Source = textBox;
                    binding.Path = new PropertyPath("IsKeyboardFocused");
                    BindingOperations.SetBinding(popup, Popup.StaysOpenProperty, binding);

                    var inputText = zoomTextTooltip.AddBlockString(textBox.Text.Trim(), zoomTextTooltip.BlockCount);
                    textBlock.Text = inputText;
                    popup.Child = textBlock;

                    textBox.GotFocus += (sender, eventArgs) =>
                                               {
                                                   popup.PlacementTarget = textBox;
                                                   popup.Placement = PlacementMode.Top;
                                                   popup.IsOpen = true;
                                               };
                    textBox.TextChanged += (sender, eventArgs) =>
                                               {
                                                   var addBlockString = zoomTextTooltip.AddBlockString(textBox.Text.Trim(), zoomTextTooltip.BlockCount);
                                                   textBlock.Text = addBlockString;
                                               };

                    textBox.LostFocus += (sender, eventArgs) =>
                                             {
                                                 popup.IsOpen = false;
                                             };
                }
                ));
        //字符串截取
        private string AddBlockString(string input, int count)
        {
            if (count == 0)
                return input;
            var blockinput = string.Empty;
            var length = Math.Ceiling((double)input.Length / count);
            for (int i = 0; i < length; i++)
            {
                var firstStart = i * count;
                var endString = input.Length - firstStart;
                if (endString < count)
                {
                    blockinput += input.Substring(firstStart);
                }
                else
                {
                    blockinput += input.Substring(firstStart, count);
                    blockinput += " ";
                }

            }
            return blockinput;
        }
        public double CustomFontSize
        {
            get { return (double)GetValue(CustomFontSizeProperty); }
            set { SetValue(CustomFontSizeProperty, value); }
        }

        public static readonly DependencyProperty CustomFontSizeProperty =
            DependencyProperty.Register("CustomFontSize", typeof(double), typeof(ZoomTextTooltip), new UIPropertyMetadata(12.0));

        public Brush CustomBackground
        {
            get { return (Brush)GetValue(CustomBackgroundProperty); }
            set { SetValue(CustomBackgroundProperty, value); }
        }

        public static readonly DependencyProperty CustomBackgroundProperty =
            DependencyProperty.Register("CustomBackground", typeof(Brush), typeof(ZoomTextTooltip), new UIPropertyMetadata(Brushes.White));

        public Brush CustomForeground
        {
            get { return (Brush)GetValue(CustomForegroundProperty); }
            set { SetValue(CustomForegroundProperty, value); }
        }

        public static readonly DependencyProperty CustomForegroundProperty =
            DependencyProperty.Register("CustomForeground", typeof(Brush), typeof(ZoomTextTooltip), new UIPropertyMetadata(Brushes.Black));

        public int BlockCount
        {
            get { return (int)GetValue(BlockCountProperty); }
            set { SetValue(BlockCountProperty, value); }
        }

        public static readonly DependencyProperty BlockCountProperty =
            DependencyProperty.Register("BlockCount", typeof(int), typeof(ZoomTextTooltip), new UIPropertyMetadata(0));



    }
<TextBox  x:Name="tb" /> 
<ControlTest:ZoomTextTooltip CustomFontSize="20"  ZoomText="{Binding ElementName=tb}" BlockCount="4"/>

图示:

image

WPF设计的程序,BLEND做的界面,在XP系统中控件有轻微的错位,部分字体不清楚,部分控件的边缘毛糙,是什么地方做错了?

在win7系统中很正常. 字体设置成display了, 我现在觉得好像不是字体的问题, 因为在xp中整个界面都和win7中有差别, 请帮忙看看问题出在哪里呢?

这是在win7中的截图, 都很正常的

这是xp中的截图, 请看左边的章节位置是一个listBox,显示很正常, 但是其他这些控件就不正常, 加入书签 按钮往左偏了一点,  上面那块黑色很毛糙的地方是一个rectangle , 右边 新宋体 那里是一个comboBox,这个控件边缘还多了一个浅蓝色的边框…. 显示文字的地方是一个richTextBox, 这些文字也是很不清楚, 都像波浪一样的 , 请帮忙看一下吧, 到底是什么地方出了问题

我又测了一下, 是在一部分xp系统上有问题, 有的很正常, 请问有没有可以解决的方法呢, 谢谢了…


Min Zhu

Min Zhu于 

你好,

根据你的描述,非常大的可能是显卡对WPF的兼容性的问题。建议你禁用这些机器上的硬件加速功能,观察一下问题是否仍然存在。如果问题得到修复了,那么就基本可以确认是显卡的问题,建议你先升级对应的显卡驱动程序到最新的版本。如果最新的显卡驱动程序仍然存在这样的问题,那就只能在这些机器上禁用硬件加速功能或者更新硬件了。

WPF Step By Step 完整布局介绍

回顾

        上一篇,我们介绍了基本控件及控件的重要属性和用法,我们本篇详细介绍WPF中的几种布局容器及每种布局容器的使用场景,当

然这些都是本人在实际项目中的使用经验,可能还存在错误之处,还请大家指出。

本文大纲

1、Grid

2、StackPanel

3、DockPanel

4、WrapPanel

Grid

1、Row和Column

我们下面来介绍Grid的行的用法,及我们在UI设计过程中需要注意的细节。

由于前面我们在第一章中已经介绍了基本的关于Grid的表格行和列的定义及相关属性,为了防止大家遗忘,我们这里再次介绍下:

image

为了加深大家对Grid布局的印象,我们这里加入控件来展示效果。

下面在每个单元格都加入子控件

image

上面指定了控件在Grid表格中的哪一行那一列,如果我们的某个控件跨行或者跨列如何做呢?

image

关于跨行和跨列一样,只不过将Grid.ColumnSpan换成Grid.RowSpan。

下面介绍,在Grid如何将控件设置为自适应宽度和高度,或者是固定宽度或固定高度时,应该注意的细节。

image

1、自适应区域:

image

2、顶部对齐或底部对齐

image

对于顶部对齐和底部对齐,相对来说都一样。

3、左右对齐时:

image

4、下面来举个例子,我们来如何分析,根据原型来使用Grid布局来达到要求和目标:

例如下图:

image

我们以博客园为例,可能例子不太合适,但是如果我们想做一个博客园的桌面版,保持风格一致的情况下,如果我们使用Grid布局如何来布局呢?

A、有Logo图片,上面还有设置等菜单,所以,我们可以吧这块设置为二行,这样比较容易区分页面的布局和设置

B、下面有几个二级菜单,新闻、博问等 一行

C、左侧有网站分类。必须1列

D、右侧有内容区。上面有区分首页、精华、候选、新闻、关注等、1列

E、右侧有找找看、还有最新新闻等 1列。

F、最下面,肯定还有状态栏,如果我们开发桌面系统。1行

 

根据上面的分析,我们的Grid表格至少5行、3列

关于其他的设计,我们通过Grid表格的组合来进行控制。

下面我们就来实现下:

先设置大体布局如下:

image

关于上述布局的具体实现如下:

<Window x:Class=”Samples.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
Title=”MainWindow” Height=”600″ Width=”800″>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height=”20″/>
<RowDefinition Height=”50″/>
<RowDefinition Height=”30″/>
<RowDefinition Height=”*”/>
<RowDefinition Height=”30″/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=”150″/>
<ColumnDefinition Width=”*”/>
<ColumnDefinition Width=”200″/>
</Grid.ColumnDefinitions>
<Grid Grid.Column=”1″ Grid.ColumnSpan=”2″>
<StackPanel Orientation=”Horizontal” HorizontalAlignment=”Right”>
<Button Content=”何戈洲” Margin=”5,0,0,0″/>
<Button Content=”我的博客” Margin=”5,0,0,0″/>
<Button Content=”短消息” Margin=”5,0,0,0″/>
<Button Content=”设置” Margin=”5,0,0,0″/>
<Button Content=”退出” Margin=”5,0,0,0″/>
</StackPanel>
</Grid>
<Grid Grid.Column=”0″ Grid.Row=”1″>
<Image Source=”/Samples;Component/Images/logo_small.gif” />
</Grid>
<Grid Grid.Column=”0″ Grid.ColumnSpan=”3″ Grid.Row=”2″>
<StackPanel Orientation=”Horizontal”>
<Button Margin=”5,0,0,0″>园子</Button>
<Button Margin=”5,0,0,0″>新闻</Button>
<Button Margin=”5,0,0,0″>博问</Button>
<Button Margin=”5,0,0,0″>闪存</Button>
<Button Margin=”5,0,0,0″>网摘</Button>
<Button Margin=”5,0,0,0″>招聘</Button>
<Button Margin=”5,0,0,0″>专题</Button>
<Button Margin=”5,0,0,0″>知识</Button>
</StackPanel>
</Grid>
<Grid Grid.Column=”0″ Grid.ColumnSpan=”3″ Grid.Row=”3″>
<Image Source=”/Samples;Component/Images/main.png” />
</Grid>
<Grid Grid.Column=”0″ Grid.ColumnSpan=”3″ Grid.Row=”4″>
<StackPanel Orientation=”Horizontal” HorizontalAlignment=”Center”>
<Button Margin=”5,0,0,0″>关于我们</Button>
<Button Margin=”5,0,0,0″>联系我们</Button>
<Button Margin=”5,0,0,0″>广告服务</Button>
<Button Margin=”5,0,0,0″>人才服务</Button>
<Button Margin=”5,0,0,0″>版权</Button>
</StackPanel>
</Grid>
</Grid>
</Window>

 

从上面的代码可以看出来,非常的简单,Grid特别适合软件系统的整体布局,在实际的项目中通过Grid与其他的布局控件相结合一起完成页面的整体布局。

StackPanel

StackPanel 适合水平或者垂直方向的布局,在上面的例子中我们大量的使用该种布局方式。适合局部区域的布局。比如博客园中的如下区域就可以采用StackPanel进行布局。

image

image

image

对于这类的固定的区域,我们可以不适用Grid来进行布局,使用StackPanel也可以达到目标。

我们来使用StackPanel来进行布局

<StackPanel Orientation=”Vertical” VerticalAlignment=”Stretch”>
<GroupBox Header=”网站分类” Height=”Auto”>
<StackPanel Orientation=”Vertical”>
<Button Content=”.NET技术(16)”/>
<Button Content=”编程语言(13)”/>
<Button Content=”软件设计(3)”/>
<Button Content=”Web前端(16)”/>
<Button Content=”软件工程(26)”/>
</StackPanel>
</GroupBox>
<GroupBox Header=”链接” Height=”Auto”>
<StackPanel Orientation=”Vertical”>
<Button Content=”反馈和建议”/>
<Button Content=”官方博客”/>
<Button Content=”电子期刊” />
<Button Content=”人才服务”/>
<Button Content=”博客模板”/>
</StackPanel>
</GroupBox>
</StackPanel>

运行效果如下:

image

与预期的效果相同,对于其他的模块,我们也可以在局部,对于水平或者垂直方向要求进行布局的,我们都可以采用StackPanel来进行布局。

下面我们来看看横向布局的例子:

image

我们通过表格中的使用对StackPanel的停靠定位,进而通过Stackpanel对内部的子控件的停靠方向设置,我们通过如下代码实现上述效果:

<StackPanel Orientation=”Horizontal” HorizontalAlignment=”Right”>
<Button Content=”何戈洲” Margin=”5,0,0,0″/>
<Button Content=”我的博客” Margin=”5,0,0,0″/>
<Button Content=”短消息” Margin=”5,0,0,0″/>
<Button Content=”设置” Margin=”5,0,0,0″/>
<Button Content=”退出” Margin=”5,0,0,0″/>
</StackPanel>

StackPanel在父容器中是右对齐的。

然后再StackPanel容器中,如果也采用内容右对齐,会有什么效果呢?

<StackPanel Orientation=”Horizontal” HorizontalAlignment=”Right”>
<Button Content=”何戈洲” Margin=”5,0,0,0″ HorizontalAlignment=”Right”/>
<Button Content=”我的博客” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”短消息” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”设置” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”退出” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
</StackPanel>

设置子控件的停靠方式时,不会起到任何作用,默认情况下,Stack的水平布局时,从左至右。

<StackPanel Orientation=”Horizontal” HorizontalAlignment=”Right” FlowDirection=”RightToLeft”>
<Button Content=”何戈洲” Margin=”5,0,0,0″ HorizontalAlignment=”Right”/>
<Button Content=”我的博客” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”短消息” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”设置” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”退出” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
</StackPanel>

修改了FlowDirection设置了StackPanel的方向后,所有的子控件,都是从右向左方向进行绘制和显示,效果如下:

image

所以对于StackPanel我们基本上是用上述的属性和对StackPanel的停靠方式进行设置后,即可满足布局的要求。

DockPanel

DockPanel停靠容器,专门负责自适应窗口的布局,之前我们介绍了DockPanel的布局设置,这里再回顾下:

<DockPanel>
<StackPanel DockPanel.Dock=”Top” Height=”0″>
</StackPanel>
<StackPanel DockPanel.Dock=”Left” Height=”0″>
</StackPanel>
<StackPanel DockPanel.Dock=”Bottom” Height=”0″>
</StackPanel>
<StackPanel DockPanel.Dock=”Right” Orientation=”Vertical” Width=”200″>
<GroupBox Header=”最新新闻” Height=”160″>
<StackPanel Orientation=”Vertical”>
<Button Content=”宅急送近日宣布降价抢”/>
<Button Content=”腾讯联手华为操盘四核手机”/>
<Button Content=”Windows 8各版本区别与售价”/>
<Button Content=”数方程将无线网络带宽提高一个数量级”/>
<Button Content=”中移动:Lumia 920T将于11月上市”/>
<Button Content=”Windows 8下一站:10月25日纽约”/>
</StackPanel>
</GroupBox>
<GroupBox Header=”48小时阅读排行榜” Height=”160″>
<StackPanel Orientation=”Vertical”>
<Button Content=”子用户-角色-权限-菜单 浅谈:子账户设计方案”/>
<Button Content=”网站已恢复正常,让大家久等了”/>
<Button Content=”拿什么拯救你,我的51Job简历?——UBB漏洞”/>
<Button Content=”这些年我们没用过的JS”/>
<Button Content=”多少钱才可让人重拾理想”/>
<Button Content=”准备购买的Dell服务器的硬件配置”/>
</StackPanel>
</GroupBox>
</StackPanel>
<StackPanel >

<Button Content=” 我铺满”/>

</StackPanel>
</DockPanel>

上面的DockPanel在进行自适应布局时,默认最后的一个区域时默认填充,可以理解为fill。而必须制定其他的区域后,该设置才有效,所以,我们上面设置了top,left,bottom 占用的空间都是0,这样,系统会将最后的一个子区域填充。

上面设置后的效果如下。

image

当然,这个页面的整体,我们也可以采用DockPanel进行布局,布局的效果,完全可以达到上述效果,下面我们来使用DockPanel来对整体进行布局吧。

最终的代码如下:

<DockPanel>
<StackPanel DockPanel.Dock=”Top” Height=”100″>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”20″/>
<RowDefinition Height=”50″/>
<RowDefinition Height=”30″/>
</Grid.RowDefinitions>
<Grid >
<StackPanel Orientation=”Horizontal” HorizontalAlignment=”Right” FlowDirection=”RightToLeft”>
<Button Content=”何戈洲” Margin=”5,0,0,0″ HorizontalAlignment=”Right”/>
<Button Content=”我的博客” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”短消息” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”设置” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
<Button Content=”退出” Margin=”5,0,0,0″  HorizontalAlignment=”Right”/>
</StackPanel>
</Grid>
<Grid  Grid.Row=”1″>
<Image Source=”/Samples;Component/Images/logo_small.gif” HorizontalAlignment=”Left”/>
</Grid>
<Grid  Grid.Row=”2″>
<StackPanel Orientation=”Horizontal”>
<Button Margin=”5,0,0,0″>园子</Button>
<Button Margin=”5,0,0,0″>新闻</Button>
<Button Margin=”5,0,0,0″>博问</Button>
<Button Margin=”5,0,0,0″>闪存</Button>
<Button Margin=”5,0,0,0″>网摘</Button>
<Button Margin=”5,0,0,0″>招聘</Button>
<Button Margin=”5,0,0,0″>专题</Button>
<Button Margin=”5,0,0,0″>知识</Button>
</StackPanel>
</Grid>
</Grid>
</StackPanel>
<StackPanel DockPanel.Dock=”Bottom” Height=”30″ Orientation=”Horizontal” HorizontalAlignment=”Center”>
<Button Margin=”5,0,0,0″>关于我们</Button>
<Button Margin=”5,0,0,0″>联系我们</Button>
<Button Margin=”5,0,0,0″>广告服务</Button>
<Button Margin=”5,0,0,0″>人才服务</Button>
<Button Margin=”5,0,0,0″>版权</Button>
</StackPanel>
<StackPanel DockPanel.Dock=”Left” Width=”150″>
<StackPanel Orientation=”Vertical” VerticalAlignment=”Stretch”>
<GroupBox Header=”网站分类” Height=”Auto”>
<StackPanel Orientation=”Vertical”>
<Button Content=”.NET技术(16)”/>
<Button Content=”编程语言(13)”/>
<Button Content=”软件设计(3)”/>
<Button Content=”Web前端(16)”/>
<Button Content=”软件工程(26)”/>
</StackPanel>
</GroupBox>
<GroupBox Header=”链接” Height=”Auto”>
<StackPanel Orientation=”Vertical”>
<Button Content=”反馈和建议”/>
<Button Content=”官方博客”/>
<Button Content=”电子期刊” />
<Button Content=”人才服务”/>
<Button Content=”博客模板”/>
</StackPanel>
</GroupBox>
</StackPanel>
</StackPanel>
<StackPanel DockPanel.Dock=”Right” Orientation=”Vertical” Width=”200″>
<GroupBox Header=”最新新闻” Height=”160″>
<StackPanel Orientation=”Vertical”>
<Button Content=”宅急送近日宣布降价抢”/>
<Button Content=”腾讯联手华为操盘四核手机”/>
<Button Content=”Windows 8各版本区别与售价”/>
<Button Content=”数方程将无线网络带宽提高一个数量级”/>
<Button Content=”中移动:Lumia 920T将于11月上市”/>
<Button Content=”Windows 8下一站:10月25日纽约”/>
</StackPanel>
</GroupBox>
<GroupBox Header=”48小时阅读排行榜” Height=”160″>
<StackPanel Orientation=”Vertical”>
<Button Content=”子用户-角色-权限-菜单 浅谈:子账户设计方案”/>
<Button Content=”网站已恢复正常,让大家久等了”/>
<Button Content=”拿什么拯救你,我的51Job简历?——UBB漏洞”/>
<Button Content=”这些年我们没用过的JS”/>
<Button Content=”多少钱才可让人重拾理想”/>
<Button Content=”准备购买的Dell服务器的硬件配置”/>
</StackPanel>
</GroupBox>
</StackPanel>
<StackPanel >
<Button Content=” 我铺满”/>
</StackPanel>
</DockPanel>

 

运行上述代码效果如下:

image

通过DockPanel完成对整体的布局,然后内部结合一些其他的布局控件,完成局部的细节的布局。

 

 

 

WrapPanel

WrapPanel容器我们也介绍过,该容器可以看做自动换行功能的StackPanel容器。下面我们就来分析下该容器的一般应用场景。

image 我们看到了windows8中的如下页面,如果我们仿制该页面的时候,其实我们可以采用wrappanel来实现自动的换行,下面我们来试试吧

最终代码如下:

<Window x:Class=”Samples.Window8Window”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
Title=”Window8Window” Height=”600″ Width=”800″>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=”50″/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<Grid Grid.Row=”0″>
<Label Content=”开始” FontFamily=”微软雅黑” FontSize=”30″/>
</Grid >
<Grid Grid.Row=”1″>
<WrapPanel Orientation=”Horizontal” ItemHeight=”100″ ItemWidth=”190″>
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
<Image Source=”/Samples;Component/Images/logo_small.gif” />
</WrapPanel>
</Grid>
</Grid>
</Window>

 

运行后,效果如下:

image

当然,我们的界面效果,还打不到美感,但是的确是自动换行。我们将水平方向,修改为垂直方向后,运行:

image

运行查看效果。

image

通过上面的简单案例,我们基本上知道了wrapPanel的用法。

 

总结

通过上面的介绍和demo的演示,我们知道了如何在项目中什么情况下,使用什么样的布局容器,通过实际的案例,我们更容易理解和掌握布局的模式。错误之处,还请大家反馈,我及时改正,谢谢!

WPF注销系统与退出系统实现

WPF在系统设计的时候少不了登陆,登出,退出系统!

那么注销系统和退出系统究竟该怎样做呢!

首先行不通的事情就是直接关闭登陆页面,也就是起始页面,这样的话系统真的就退出,因此采用的方法应该是在登陆成功之后,隐藏掉登陆页面。

那么我们又怎样来进行登出操作呢?退出系统操作呢?

WPF注销系统与退出系统实现

 

在关闭的时候,我们可能在MainWindow中直接有退出系统按钮,那么我们在点击的时候直接调用Application.Current.ShutDownOK,关闭了,没有问题,但是如果我们不是点击的退出系统按钮,而是直接点击Window自带的关闭呢,结果是什么?可想而知,我们只是退出了当前的主页,而没有退出系统,因为还有登陆页面在隐藏。

因此我们用上面的关闭方法是行不通的,那么到底该怎样做呢,其实很简单,我们知道每个窗体都有Closing的事件,只要我们捕捉到加以利用,一切就OK了,因此我们在MianWindow窗体初始化的时候就监听它的Closing事件,这样无论我们点击了什么,只要当前的窗体关闭了,就相当于关闭了整个系统。

问题还是存在,那么如果我们又想注销系统怎么办呢?当然我们得关闭MainWindow,但是我们不是真想关闭系统而是想显示Login窗体,这样我们以上的做法不是不合理了?难道要改套路不成?

不是的,我们可以设置在MianWindow中设置一个bool的全局变量,默认值为trueClosing事件中,我们只需要判断一下这个变量就可以了,如果为true,那么就关闭系统,如果为false,则显示Login窗体,(当然,在那个页面进行控制,则在哪个页面就应该传递过来Login窗体的对象,我认为比较好用的方法是定义一个属性,通过传递来进行,例如在Login打开MianWidow的时候,我们就将MainWindow.Login=this),清空该用户的信息,OK,那么我们什么时候设置那个全局变量呢?当然是在我们点击注销按钮的时候了,在点击注销按钮的时候,就两个动作,一个是将全局的变量设置成False,另一重要,就是关闭自身,this.close()OK 这样就完成了。

C# WPF 中用代码模拟鼠标和键盘的操作

原文地址
C#开发者都知道,在Winform开发中,SendKeys类提供的方法是很实用的。但是可惜的是,在WPF中不能使用这个方法了。
我们知道,在WPF中非UI线程刷新UI线程,需要使用Dispatcher.Invoke((Action)delegate { /* Your
code is put here */ });
方法。这里调用System.Windows.Forms.SendKeys.Send()方法会报错。

下面这个代码文件做了一个很好的包装,可以下载后参考:
Simulation.zip

如何使用呢?
很简单, 要敲一个键, 比如回车:
Keyboard.Press(Key.Enter);
Keyboard.Release(Key.Enter);

要敲一个组合键:比如Alt+F4:
Keyboard.Press(Key.LeftAlt);
Keyboard.Press(Key.F4);
Keyboard.Release(Key.LeftAlt);
Keyboard.Release(Key.F4);

要敲一段文字:
Keyboard.Type(“notepad”);

鼠标与之类似,比如:
Mouse.MoveTo(new System.Drawing.Point(x, y));
Mouse.Click(MouseButton.Right);

Simulation类的定义如下:

//定义是这样的:

[DllImport("user32.dll", SetLastError = true)]
internal static extern int SendInput(int nInputs, ref INPUT mi, int cbSize);

//其中的INPUT结构表示一个键盘或鼠标操作:

[StructLayout(LayoutKind.Sequential)]
internal struct INPUT
{
internal int type;
internal INPUTUNION union;
};
[StructLayout(LayoutKind.Explicit)]
internal struct INPUTUNION
{
[FieldOffset(0)]
internal MOUSEINPUT mouseInput;
[FieldOffset(0)]
internal KEYBDINPUT keyboardInput;
};
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEINPUT
{
internal int dx;
internal int dy;
internal int mouseData;
internal int dwFlags;
internal int time;
internal IntPtr dwExtraInfo;
};
[StructLayout(LayoutKind.Sequential)]
internal struct KEYBDINPUT
{
internal short wVk;
internal short wScan;
internal int dwFlags;
internal int time;
internal IntPtr dwExtraInfo;
};
[Flags]
internal enum SendMouseInputFlags
{
Move = 0x0001,
LeftDown = 0x0002,
LeftUp = 0x0004,
RightDown = 0x0008,
RightUp = 0x0010,
MiddleDown = 0x0020,
MiddleUp = 0x0040,
XDown = 0x0080,
XUp = 0x0100,
Wheel = 0x0800,
Absolute = 0x8000,
};

WPF DocumentViewer隐藏工具栏和搜索栏

<Style x:Key=”{x:Type DocumentViewer}” TargetType=”{x:Type DocumentViewer}”>

<Setter Property=”Foreground” Value=”{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}” />

<Setter Property=”Background” Value=”{DynamicResource {x:Static SystemColors.ControlBrushKey}}” />

<Setter Property=”FocusVisualStyle” Value=”{x:Null}” />

<Setter Property=”Template”>

<Setter.Value>

<ControlTemplate TargetType=”{x:Type DocumentViewer}”>

<Border BorderThickness=”{TemplateBinding BorderThickness}” BorderBrush=”{TemplateBinding BorderBrush}” Focusable=”False”>

<Grid KeyboardNavigation.TabNavigation=”Local”>

<Grid.Background>

<SolidColorBrush Color=”{DynamicResource ControlLightColor}” />

</Grid.Background>

<Grid.RowDefinitions>

<RowDefinition Height=”Auto” />

<RowDefinition Height=”*” />

<RowDefinition Height=”Auto” />

</Grid.RowDefinitions>

<ScrollViewer Grid.Row=”1″ CanContentScroll=”true” HorizontalScrollBarVisibility=”Auto” x:Name=”PART_ContentHost” IsTabStop=”true”>

<ScrollViewer.Background>

<LinearGradientBrush EndPoint=”0.5,1″ StartPoint=”0.5,0″>

<GradientStop Color=”{DynamicResource ControlLightColor}” Offset=”0″ />

<GradientStop Color=”{DynamicResource ControlMediumColor}” Offset=”1″ />

</LinearGradientBrush>

</ScrollViewer.Background>

</ScrollViewer>

</Grid>

</Border>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

关于 WPF PrintDialog 设置打印纸张高度与宽度问题。

    • 问题如下:

      我通过WPF进行打印,WPF中PrintDialog默认纸张大小应该是A4,宽度是700多左右,高度是1000多。当我想要在X轴1000的位置打印文字时,总是打印不出来。因此我想通过设置打印纸张的大小来让X轴1000处的文字打印出来。代码如下。设置PageMediaSize为 ISOA0,应该是3000*4000多。但是我还是无法将X轴1000处的文字打印出来。网上搜了很久无果。因此在这里提问。希望能解决这个问题。代码如下。

      private void button_SinglePointPrint_Click(object sender, RoutedEventArgs e)
      {
      PrintDialog pDialog = new PrintDialog();
      pDialog.PrintTicket = pt;
      pDialog.PrintQueue = pq;
      pDialog.PrintTicket.PageMediaSize = new PageMediaSize(PageMediaSizeName.ISOA0);
      if (pDialog.ShowDialog() == true)
      {
      pt = pDialog.PrintTicket;
      pq = pDialog.PrintQueue;

      DrawingVisual vis = new DrawingVisual();
      DrawingContext dc = vis.RenderOpen();

      dc.DrawText(pLib.GetFormattedText(textBox_SinglePointText.Text, TextAlignment.Center, new Typeface(fontName), fontSize), new Point(1000,100));
      dc.Close();

      pDialog.PrintVisual(vis, “TestSinglePoint”);
      }
      }

      2014年4月21日 15:38
      kouxuelong 的头像

      0 分数

答案

  • 你好,

    你可以通过设置PrintTicket.PageMediaSizeSize,然后Transform你的窗体大小。请查看下面代码:

     private void _print()
     {
          PrintDialog printDlg = new System.Windows.Controls.PrintDialog();
    
           PrintTicket pt = printDlg.PrintTicket;
           Double printableWidth = pt.PageMediaSize.Width.Value;
           Double printableHeight = pt.PageMediaSize.Height.Value;
    
           Double xScale = (printableWidth - xMargin * 2) / printableWidth;
           Double yScale = (printableHeight - yMargin * 2) / printableHeight;
    
           this.Transform = new MatrixTransform(xScale, 0, 0, yScale, xMargin, yMargin);
    
        //now print the visual to printer to fit on the one page.
         printDlg.PrintVisual(this, "Print Page");
     }

    谢谢!

WPF小知识,MessageBox的多种用法

我们在程序中经常会用到MessageBox。

现将其常见用法总结如下:

1.MessageBox.Show(“Hello~~~~”);

最简单的,只显示提示信息。

2.MessageBox.Show(“There are something wrong!”,”ERROR”);

可以给消息框加上标题。

3.if (MessageBox.Show(“Delete this user?”, “Confirm Message”, MessageBoxButtons.OKCancel) == DialogResult.OK)

{

//delete

}

询问是否删除时会用到这个。

4.if (MessageBox.Show(“Delete this user?”, “Confirm Message”, MessageBoxButtons.OKCancel,MessageBoxIcon.Question) == DialogResult.OK)

{

//delete

}

可以给MessageBox加上一个Icon,.net提供常见的Icon共选择。

5.if (MessageBox.Show(“Delete this user?”, “Confirm Message”, MessageBoxButtons.OKCancel, MessageBoxIcon.Question,MessageBoxDefaultButton.Button2) == DialogResult.OK)

{

//delete

}

可以改变MessageBox的默认焦点,如下:

6.if (MessageBox.Show(“Delete this user?”, “Confirm Message”, MessageBoxButtons.OKCancel, MessageBoxIcon.Question,MessageBoxDefaultButton.Button2,MessageBoxOptions.RtlReading) == DialogResult.OK)

{

//delete

}

反向显示:

7.if (MessageBox.Show(“Delete this user?”, “Confirm Message”, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign,true) == DialogResult.OK)

{

//delete

}

添加Help按钮:

8.if (MessageBox.Show(“Delete this user?”, “Confirm Message”, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1, MessageBoxOptions.RtlReading, @”/folder/file.htm”) == DialogResult.OK)

{

//delete

}

指定帮助文件的路径,点击即可打开该路径下的帮助文件。

9.//HelpNavigator指定常数来指示要显示的帮助文件元素。Find 帮助文件将打开到搜索页。

if (MessageBox.Show(“Delete this user?”, “Confirm Message”, MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1, MessageBoxOptions.RtlReading, @”/folder/file.htm”, HelpNavigator.Find) == DialogResult.OK)

{

//delete

}

还有一些用法,不是太实用这里就不一一介绍了,有兴趣的朋友可以参考下这里:MSDN的MessageBox类。

========================================================================
【函数】 <整型> MessageBox(<字符串> Text, <字符串> Title, <整型> nType,MessageBoxIcon);
【函数说明】 弹出一个消息框。
【语法】
参数:
Text <字符串>,消息框的正文;
Title <字符串>,消息框的标题;
nType <整型>,消息框的类型。
返回值:<整型>,用户在消息框上点击关闭时的选择的按钮。 MessageBoxIcon:对话框上显示的图标样式。

【说明】
MessageBox(“消息内容”, “返回值 确定1”,MessageBoxButtons.OK,MessageBoxIcon.Question);
MessageBox(“消息内容”,, “返回值 确定1 取消2”,MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk);
MessageBox(“消息内容”, “返回值 终止3 重试4 忽略5”,MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Error);
MessageBox(“消息内容”, “返回值 是6 否7 取消2”,MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation);
MessageBox(“消息内容”, “返回值 是6 否7”,MessageBoxButtons.YesNo, MessageBoxIcon.Hand);
MessageBox(“消息内容”, “返回值 重试4 取消2”,MessageBoxButtons.RetryCancel, MessageBoxIcon.Information);

MessageBoxIcon: 所有图标样式

MessageBoxIcon.Question MessageBoxIcon.Asterisk MessageBoxIcon.Information MessageBoxIcon.Error MessageBoxIcon.Stop MessageBoxIcon.Hand MessageBoxIcon.Exclamation MessageBoxIcon.Warning MessageBoxIcon.None

MessageBox函数MessageBox()函数MessageBox是标准的windows Api函数只能在CWnd类的继承类中使用,在C#中使用时,通常用MessageBox的show方法来实现对话框的弹出,命名空间System.Windows.Forms

应用实例:

DialogResult 是枚举类可以用枚举值直接比较MessageBox的返回值也可以转换为整型后再比较。如下:DialogResult r1 = MessageBox.Show ( “是否确定?” , “垃圾处理!” , MessageBoxButtons.AbortRetryIgnore , MessageBoxIcon.Question ) ;
int ss1=(int)r1 ;
if ( ss1==3 ){ }
if ( ss1==4 ){ }
if ( ss1==5){ }

或者是

if (DialogResult.Yes == MessageBox.Show(“232”, “”, MessageBoxButtons.YesNo, MessageBoxIcon.Information,MessageBoxDefaultButton.Button1))
{
MessageBox.Show(“122”);
}

WPF有一个 button, button 的背景是一张图片, 当我运行程序后, 点击button的时候, button会有按下的效果,但同时 背景图片也消失了, 等松开鼠标的时候,背景图片又回来了,

回复次数:4

WPF后台动态调用样式文件

应用场合:如果您的WPF应用程序设置WPF运行一个实例代码后,App.xaml文件中对样式资源字典文件的引用将失效.

解决办法1:在App.xaml.cs文件中用反射动态调用另外一个DLL项目中的样式文件即可

详细操作介绍如下:

1、WPF设置只运行一个实例代码:

App.xaml文件代码如下:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source=”ButtonStyle.xaml”/>
</ResourceDictionary>
</Application.Resources>
</Application>

App.xaml.cs文件代码如下:

//添加引用
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace WpfUI
{

/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{

public App()
{
}

/// <summary>
/// 要设置App.xaml的文件属性中生成操作=无
/// </summary>
[STAThread]
public static void Main()
{
App myApp = new App();
myApp.ShutdownMode = ShutdownMode.OnExplicitShutdown;
myApp.Run();
}

private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
if (e.Exception is InvalidOperationException)
e.Handled = true;
}

protected override void OnStartup(StartupEventArgs e)
{
//获取当前运行WPF程序的进程实例
Process process = Process.GetCurrentProcess();
//遍历WPF程序的同名进程组
foreach (Process p in Process.GetProcessesByName(process.ProcessName))
{
if (p.Id != process.Id && (p.StartTime – process.StartTime).TotalMilliseconds <= 0)
{
p.Kill();//关闭进程
return;
}
}
base.OnStartup(e);
//启动登陆窗体,

MainWindow myWindow = new MainWindow();

myWindow.Show();
}

}

}

2、ButtonStyle.xaml样式文件内容如下:

<ResourceDictionary xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml“>
<!–按钮样式–>
<Style x:Key=”RedButtonStyle” TargetType=”{x:Type Button}”>
<Setter Property=”Foreground” Value=”Red”/>
<Setter Property=”Background” Value=”Silver”/>
<Setter Property=”Height” Value=”23″/>
</Style>

</ResourceDictionary>

3、MainWindow.xaml文件内容如下:

<Window x:Class=”WpfUI.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
Title=”MainWindow” Height=”350″ Width=”525″>
<Grid>
<Button Content=”Button” Style=”{StaticResource RedButtonStyle}” Height=”23″ HorizontalAlignment=”Left” Margin=”102,66,0,0″ Name=”button1″ VerticalAlignment=”Top” Width=”75″ />
</Grid>
</Window>
4、运行程序后发现按钮样式RedButtonStyle总提示找不到

5、 解决办法如下:

第一步: 新建一个Windows–类库项目WpfThems,将ButtonStyle.xaml拷贝过去, 设置ButtonStyle.xaml文件的属性生成操作为 “Page”,之后生成WpfThems.dll文件

第二步:在当前项目WpfUI中添加WpfThems项目引用

第三步:修改App.xaml.cs 文件代码:添加一个函数LoadStyleResource并修改OnStartup函数内容。修改后的App.xaml.cs文件内容如下:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
//添加引用
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace WpfUI
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{

public App()
{
}

/// <summary>
/// 设置WpfUI项目中的App.xaml的文件属性中”生成操作”为”无”
/// 设置WpfThemes项目中的ButtonStyle.xaml的文件属性中”生成操作”为”Page”
/// </summary>
[STAThread]
public static void Main()
{
App myApp = new App();
myApp.ShutdownMode = ShutdownMode.OnExplicitShutdown;
myApp.Run();
}

/// <summary>
/// 重载应用程序启动函数
/// </summary>
/// <param name=”e”></param>
protected override void OnStartup(StartupEventArgs e)
{
//获取当前运行WPF程序的进程实例
Process process = Process.GetCurrentProcess();
//遍历WPF程序的同名进程组
foreach (Process p in Process.GetProcessesByName(process.ProcessName))
{
if (p.Id != process.Id && (p.StartTime – process.StartTime).TotalMilliseconds <= 0)
{
p.Kill();//关闭进程
return;
}
}
base.OnStartup(e);
//动态调用样式文件
LoadStyleResource();

//启动窗体
MainWindow myWindow = new MainWindow();
myWindow.Show();
}
private void LoadStyleResource()
{
Assembly assembly = Assembly.LoadFrom(“WpfThemes.dll”);
string packUri = @”/WpfThemes;component/ButtonStyle.xaml”;
ResourceDictionary myResourceDictionary = Application.LoadComponent(new Uri(packUri, UriKind.Relative)) as ResourceDictionary;
this.Resources.MergedDictionaries.Add(myResourceDictionary);
}
}
}

 

解决办法2:在每个窗体的xaml文件中添加对指定样式文件的引用

<Window.Resources>

<ResourceDictionary>

<ResourceDictionary.MergedDictionaries>

<ResourceDictionary Source=”ButtonStyle.xaml”/>

</ResourceDictionary.MergedDictionaries>

</ResourceDictionary>

</Window.Resources>

详细工程项目请到我的下载资源中下载: http://download.csdn.net/detail/xqf222/5582575

WPF 登录窗口关闭时打开主窗口

在WPF中设计登录窗口关闭时打开主窗口,自动生成的App.xaml不能满足要求,

1、把App.xaml的属性窗口中的生成操作设定为 无

2、添加Program类

复制代码
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            LoginWindow loginForm = new LoginWindow();
            loginForm.Init();
            bool? rt = loginForm.ShowDialog();
            loginForm.Close();
            if (rt == true)
            {
                Application App = new Application();
                App.ShutdownMode = ShutdownMode.OnMainWindowClose;
                MainWindow m_MianWindow = new MainWindow();
                App.MainWindow = m_MianWindow;
                App.Run(m_MianWindow);
            }
        }
    }
复制代码

这样就可以满足要求了

参考:http://www.mysjtu.com/page/M0/S613/613036.html

 

方法二:

最近看一方法,不用添加Program方法即可,代码如下:

复制代码
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;
            LoginWindow window = new LoginWindow();
            bool? dialogResult = window.ShowDialog();
            if ((dialogResult.HasValue == true) &&
                (dialogResult.Value == true))
            {
                base.OnStartup(e);
                Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
            }
            else
            {
                this.Shutdown();
            }
        }
    }
复制代码

通过Application的ShutdownMode控制进程的运行时间。

WPF 后台任务 等待动画 样例

运行效果:

前台代码:

[csharp] view plain copy

  1. <Window x :Class=“Waiting.Window1”
  2.         xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation”
  3.         xmlns:x =“http://schemas.microsoft.com/winfx/2006/xaml”
  4.         Title=“后台忙” Height=“94.635” Width=“197.361”>
  5.     <Grid >
  6.         <Button Content =“开始” HorizontalAlignment=“Left” Margin=“10,10,0,0” Name =“button1” VerticalAlignment=“Top” Click=“button1_Click” Width =“48” />
  7.         <Label Name =“lab_pro” Content=“” Height=“25” VerticalAlignment =“Top” HorizontalAlignment=“Left” Margin=“80,20,0,0” />
  8.         <!–动画代码,只要填写name属性即可–>
  9.         <Grid Name =“loading” Visibility=“Collapsed” Height=“41” Grid.Row =“0” VerticalAlignment=“Top” Margin=“126,10,0,0” HorizontalAlignment=“Left” Width =“42”>
  10.             <Grid.Resources>
  11.                 <DrawingBrush x :Key=“brush” Stretch=“None” AlignmentX =“Center” AlignmentY=“Top”>
  12.                     <DrawingBrush.Drawing>
  13.                         <GeometryDrawing Brush =“Black”>
  14.                             <GeometryDrawing.Geometry>
  15.                                 <EllipseGeometry RadiusX =“2” RadiusY=“5”/>
  16.                             </GeometryDrawing.Geometry>
  17.                         </GeometryDrawing>
  18.                     </DrawingBrush.Drawing>
  19.                 </DrawingBrush>
  20.             </Grid.Resources>
  21.             <Rectangle x :Name=“r01” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  22.                 <Rectangle.RenderTransform>
  23.                     <RotateTransform Angle =“0”/>
  24.                 </Rectangle.RenderTransform>
  25.             </Rectangle>
  26.             <Rectangle x :Name=“r02” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  27.                 <Rectangle.RenderTransform>
  28.                     <RotateTransform Angle =“30”/>
  29.                 </Rectangle.RenderTransform>
  30.             </Rectangle>
  31.             <Rectangle x :Name=“r03” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  32.                 <Rectangle.RenderTransform>
  33.                     <RotateTransform Angle =“60”/>
  34.                 </Rectangle.RenderTransform>
  35.             </Rectangle>
  36.             <Rectangle x :Name=“r04” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  37.                 <Rectangle.RenderTransform>
  38.                     <RotateTransform Angle =“90”/>
  39.                 </Rectangle.RenderTransform>
  40.             </Rectangle>
  41.             <Rectangle x :Name=“r05” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  42.                 <Rectangle.RenderTransform>
  43.                     <RotateTransform Angle =“120”/>
  44.                 </Rectangle.RenderTransform>
  45.             </Rectangle>
  46.             <Rectangle x :Name=“r06” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  47.                 <Rectangle.RenderTransform>
  48.                     <RotateTransform Angle =“150”/>
  49.                 </Rectangle.RenderTransform>
  50.             </Rectangle>
  51.             <Rectangle x :Name=“r07” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  52.                 <Rectangle.RenderTransform>
  53.                     <RotateTransform Angle =“180”/>
  54.                 </Rectangle.RenderTransform>
  55.             </Rectangle>
  56.             <Rectangle x :Name=“r08” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  57.                 <Rectangle.RenderTransform>
  58.                     <RotateTransform Angle =“210”/>
  59.                 </Rectangle.RenderTransform>
  60.             </Rectangle>
  61.             <Rectangle x :Name=“r09” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  62.                 <Rectangle.RenderTransform>
  63.                     <RotateTransform Angle =“240”/>
  64.                 </Rectangle.RenderTransform>
  65.             </Rectangle>
  66.             <Rectangle x :Name=“r10” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  67.                 <Rectangle.RenderTransform>
  68.                     <RotateTransform Angle =“270”/>
  69.                 </Rectangle.RenderTransform>
  70.             </Rectangle>
  71.             <Rectangle x :Name=“r11” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  72.                 <Rectangle.RenderTransform>
  73.                     <RotateTransform Angle =“300”/>
  74.                 </Rectangle.RenderTransform>
  75.             </Rectangle>
  76.             <Rectangle x :Name=“r12” Fill=“{StaticResource brush}” Opacity =“0.5” RenderTransformOrigin=“0.5,0.5”>
  77.                 <Rectangle.RenderTransform>
  78.                     <RotateTransform Angle =“330”/>
  79.                 </Rectangle.RenderTransform>
  80.             </Rectangle>
  81.             <Grid.Triggers>
  82.                 <EventTrigger RoutedEvent =“Grid.Loaded”>
  83.                     <BeginStoryboard>
  84.                         <Storyboard RepeatBehavior =“Forever”>
  85.                             <DoubleAnimation Storyboard.TargetName =“r01” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.00000” To=“0”/>
  86.                             <DoubleAnimation Storyboard.TargetName =“r02” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.08333” To=“0”/>
  87.                             <DoubleAnimation Storyboard.TargetName =“r03” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.16666” To=“0”/>
  88.                             <DoubleAnimation Storyboard.TargetName =“r04” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.24999” To=“0”/>
  89.                             <DoubleAnimation Storyboard.TargetName =“r05” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.33332” To=“0”/>
  90.                             <DoubleAnimation Storyboard.TargetName =“r06” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.41665” To=“0”/>
  91.                             <DoubleAnimation Storyboard.TargetName =“r07” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.49998” To=“0”/>
  92.                             <DoubleAnimation Storyboard.TargetName =“r08” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.58331” To=“0”/>
  93.                             <DoubleAnimation Storyboard.TargetName =“r09” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.66664” To=“0”/>
  94.                             <DoubleAnimation Storyboard.TargetName =“r10” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.74997” To=“0”/>
  95.                             <DoubleAnimation Storyboard.TargetName =“r11” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.83330” To=“0”/>
  96.                             <DoubleAnimation Storyboard.TargetName =“r12” Storyboard.TargetProperty=“Opacity” AutoReverse=“True” Duration=“0:0:0.08333” BeginTime =“0:0:0.91663” To=“0”/>
  97.                         </Storyboard>
  98.                     </BeginStoryboard>
  99.                 </EventTrigger>
  100.             </Grid.Triggers>
  101.         </Grid>
  102.     </Grid >
  103. </Window>

后台代码:

[csharp] view plain copy

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Data;
  9. using System.Windows.Documents;
  10. using System.Windows.Input;
  11. using System.Windows.Media;
  12. using System.Windows.Media.Imaging;
  13. using System.Windows.Shapes;
  14. namespace Waiting
  15. {
  16.     /// <summary>
  17.     /// Window1.xaml 的交互逻辑
  18.     /// </summary>
  19.     public partial class Window1 : Window
  20.     {
  21.         public Window1()
  22.         {
  23.             InitializeComponent();
  24.         }
  25.         BackgroundWorker bgMeet;
  26.         private void button1_Click(object sender, RoutedEventArgs e)
  27.         {
  28.             bgMeet = new BackgroundWorker ();
  29.             //能否报告进度更新
  30.             bgMeet.WorkerReportsProgress = true;
  31.             //要执行的后台任务
  32.             bgMeet.DoWork += new DoWorkEventHandler (bgMeet_DoWork);
  33.             //进度报告方法
  34.             bgMeet.ProgressChanged += new ProgressChangedEventHandler (bgMeet_ProgressChanged);
  35.             //后台任务执行完成时调用的方法
  36.             bgMeet.RunWorkerCompleted += new RunWorkerCompletedEventHandler (bgMeet_RunWorkerCompleted);
  37.             bgMeet.RunWorkerAsync(); //任务启动
  38.         }
  39.         //执行任务
  40.         void bgMeet_DoWork(object sender, DoWorkEventArgs e)
  41.         {
  42.             //开始播放等待动画
  43.             this.Dispatcher.Invoke(new Action(() =>
  44.             {
  45.                 loading.Visibility = System.Windows. Visibility.Visible;
  46.             }));
  47.             //开始后台任务
  48.             GetData();
  49.         }
  50.         //报告任务进度
  51.         void bgMeet_ProgressChanged(object sender, ProgressChangedEventArgs e)
  52.         {
  53.             this.Dispatcher.Invoke(new Action(() =>
  54.             {
  55.                 this.lab_pro.Content = e.ProgressPercentage + “%”;
  56.             }));
  57.         }
  58.         //任务执行完成后更新状态
  59.         void bgMeet_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  60.         {
  61.             loading.Visibility = System.Windows. Visibility.Collapsed;
  62.             this.Dispatcher.Invoke(new Action(() =>
  63.             {
  64.                 this.lab_pro.Content = “完成” ;
  65.             }));
  66.         }
  67.         //模拟耗时任务
  68.         public void GetData()
  69.         {
  70.             for (int i = 0; i < 6; i++)
  71.             {
  72.                 bgMeet.ReportProgress(20*i);
  73.                 System.Threading. Thread.Sleep(400);
  74.             }
  75.         }
  76.     }
  77. }

WPF 设置TextBox为空时,背景为文字提示。

<TextBox FontSize="17" Height="26" Margin="230,150,189,0" Name="txt_Account" VerticalAlignment="Top" Foreground="Indigo" TabIndex="0" BorderThickness="1">
            <TextBox.Resources>
                <VisualBrush x:Key="HelpBrush" TileMode="None" Opacity="0.3" Stretch="None" AlignmentX="Left">
                    <VisualBrush.Visual>
                        <TextBlock FontStyle="Italic" Text="请输入用户名"/>
                    </VisualBrush.Visual>
                </VisualBrush>
            </TextBox.Resources>
            <TextBox.Style>
                <Style TargetType="TextBox">
                    <Style.Triggers>
                        <Trigger Property="Text" Value="{x:Null}">
                            <Setter Property="Background" Value="{StaticResource HelpBrush}"/>
                        </Trigger>
                        <Trigger Property="Text" Value="">
                            <Setter Property="Background" Value="{StaticResource HelpBrush}"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>

WPF中解决内存泄露的几点提示与解决方法

一直以来用WPF做一个项目,但是开发中途发现内存开销太大,用ANTS Memory Profiler分析时,发现在来回点几次载入页面的操作中,使得非托管内存部分开销从起始的43.59M一直到150M,而托管部分的开销也一直持高不下,即每次申请的内存在结束后不能完全释放。在网上找了不少资料,甚受益,现在修改后,再也不会出现这种现象了(或者说,即使有也不吓人),写下几个小心得:

1. 慎用WPF样式模板合并

我发现不采用合并时,非托管内存占用率较小,只是代码的理解能力较差了,不过我们还有文档大纲可以维护。

2. WPF样式模板请共享

共享的方式最简单不过的就是建立一个类库项目,把样式、图片、笔刷什么的,都扔进去,样式引用最好使用StaticResource,开销最小,但这样就导致了一些写作时的麻烦,即未定义样式,就不能引用样式,哪怕定义在后,引用在前都不行。

3. 慎用隐式类型var的弱引用

这个本来应该感觉没什么问题的,可是不明的是,在实践中,发现大量采用var与老老实实的使用类型声明的弱引用对比,总是产生一些不能正确回收的WeakRefrense(这点有待探讨,因为开销不是很大,可能存在一些手工编程的问题)

4. 写一个接口约束一下

谁申请谁释放,基本上这点能保证的话,内存基本上就能释放干净了。我是这么做的:

复制代码
    interface IUIElement : IDisposable
    {
        /// <summary>
        /// 注册事件
        /// </summary>
        void EventsRegistion();

        /// <summary>
        /// 解除事件注册
        /// </summary>
        void EventDeregistration();
    }
复制代码

在实现上可以这样:

复制代码
 1 #region IUIElement 成员
 2 public void EventsRegistion()
 3 {
 4     this.traineeReport.SelectionChanged += new SelectionChangedEventHandler(traineeReport_SelectionChanged);
 5 }
 6 
 7 public void EventDeregistration()
 8 {
 9     this.traineeReport.SelectionChanged -= new SelectionChangedEventHandler(traineeReport_SelectionChanged);
10 }
11 
12 private bool disposed;
13 
14 ~TraineePaymentMgr()
15 {
16     ConsoleEx.Log("{0}被销毁", this);
17     Dispose(false);
18 }
19 
20 public void Dispose()
21 {
22     ConsoleEx.Log("{0}被手动销毁", this);
23     Dispose(true);
24     GC.SuppressFinalize(this);
25 }
26 
27 protected void Dispose(bool disposing)
28 {
29     ConsoleEx.Log("{0}被自动销毁", this);
30     if(!disposed)
31     {
32         if(disposing)
33         {
34             //托管资源释放
35             ((IDisposable)traineeReport).Dispose();
36             ((IDisposable)traineePayment).Dispose();
37         }
38         //非托管资源释放
39     }
40     disposed = true;
41 }
42 #endregion
复制代码

比如写一个UserControl或是一个Page时,可以参考以上代码,实现这样接口,有利于资源释放。

5. 定时回收垃圾

复制代码
DispatcherTimer GCTimer = new DispatcherTimer();
public MainWindow()
{
    InitializeComponent();
    this.GCTimer.Interval = TimeSpan.FromMinutes(10); //垃圾释放定时器 我定为每十分钟释放一次,大家可根据需要修改
  this.GCTimer.start();

    this.EventsRegistion();    // 注册事件
}

public void EventsRegistion()
{
    this.GCTimer.Tick += new EventHandler(OnGarbageCollection);
}

public void EventDeregistration()
{
    this.GCTimer.Tick -= new EventHandler(OnGarbageCollection);
}

void OnGarbageCollection(object sender, EventArgs e)
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
}
复制代码

6. 较简单或可循环平铺的图片用GeometryDrawing实现

一个图片跟几行代码相比,哪个开销更少肯定不用多说了,而且这几行代码还可以BaseOn进行重用。

复制代码
<DrawingGroup x:Key="Diagonal_50px">
    <DrawingGroup.Children>
        <GeometryDrawing Brush="#FF2A2A2A" Geometry="F1 M 0,0L 50,0L 50,50L 0,50 Z"/>
        <GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,0L 0,50L 0,25L 25,0L 50,0 Z"/>
        <GeometryDrawing Brush="#FF262626" Geometry="F1 M 50,25L 50,50L 25,50L 50,25 Z"/>
    </DrawingGroup.Children>
</DrawingGroup>
复制代码

这边是重用

<DrawingBrush x:Key="FrameListMenuArea_Brush" Stretch="Fill" TileMode="Tile" Viewport="0,0,50,50" ViewportUnits="Absolute"
Drawing="{StaticResource Diagonal_50px}"/>

上面几行代码相当于这个:

7. 使用Blend做样式的时候,一定要检查完成的代码

众所周知,Blend定义样式时,产生的垃圾代码还是比较多的,如果使用Blend,一定要检查生成的代码。

 

8. 静态方法返回诸如List<>等变量的,请使用out

比如

public static List<String> myMothod()

{...}

请改成

public static myMothod(out List<String> result)

{...}

 

9. 打针对此问题的微软补丁

3.5的应该都有了吧,这里附上NET4的内存泄露补丁地址,下载点这里 (QFE:  Hotfix request to implement hotfix KB981107 in .NET 4.0 )

这是官方给的说明,看来在样式和数据绑定部分下了点工夫啊:

  1. 运行一个包含样式或模板,请参阅通过使用 StaticResource 标记扩展或 DynamicResource 标记扩展应用程序资源的 WPF 应用程序。 创建使用这些样式或模板的多个控件。 但是,这些控件不使用引用的资源。 在这种情况的一些内存WeakReference对象和空间泄漏的控股数组后,垃圾回收释放该控件。
  2. 运行一个包含的控件的属性是数据绑定到的 WPF 应用程序DependencyObject对象。 该对象的生存期是超过控件的生存期。 许多控件时创建,一些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。
  3. 运行使用树视图控件或控件派生于的 WPF 应用程序,选择器类。 将控件注册为控制中的键盘焦点的内部通知在KeyboardNavigation类。 该应用程序创建这些控件的很多。 例如对于您添加并删除这些控件。 在本例中为某些内存WeakReference对象和容纳数组空格被泄漏后垃圾回收释放该控件。

继续更新有关的三个8月补丁,详细的请百度:KB2487367  KB2539634  KB2539636,都是NET4的补丁,在发布程序的时候,把这些补丁全给客户安装了会好的多。

10.  对string怎么使用的建议

这个要解释话就长了,下面仅给个例子说明一下,具体的大家去找找MSDN

复制代码
        string ConcatString(params string[] items)
        {
            string result = "";
            foreach (string item in items)
            {
                result += item;
            }
            return result;
        }

        string ConcatString2(params string[] items)
        {
            StringBuilder result = new StringBuilder();
            for(int i=0, count = items.Count(); i<count; i++)
            {
                result.Append(items[i]);
            }
            return result.ToString();
        }
复制代码

建议在需要对string进行多次更改时(循环赋值、连接之类的),使用StringBuilder。我已经把工程里这种频繁且大量改动string的操作全部换成了StringBuilder了,用ANTS Memory Profiler分析效果显著,不仅提升了性能,而且垃圾也少了。

 

11. 其它用上的技术暂时还没想到,再补充…

 

如果严格按以上操作进行的话,可以得到一个满意的结果:

运行了三十分钟,不断的切换功能,然后休息5分钟,回头一看,结果才17M左右内存开销,效果显著吧。

然后对于调试信息的输出,我的做法是在窗体应用程序中附带一个控制台窗口,输出调试信息,给一个类,方便大家:

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Trainee.UI.UIHelper
{
    public struct COORD
    {
        public ushort X;
        public ushort Y;
    };

    public struct CONSOLE_FONT
    {
        public uint index;
        public COORD dim;
    };

    public static class ConsoleEx
    {
        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern bool AllocConsole();

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern bool SetConsoleFont(IntPtr consoleFont, uint index);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern bool GetConsoleFontInfo(IntPtr hOutput, byte bMaximize, uint count, [In, Out] CONSOLE_FONT[] consoleFont);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern uint GetNumberOfConsoleFonts();

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32", CharSet = CharSet.Auto)]
        internal static extern COORD GetConsoleFontSize(IntPtr HANDLE, uint DWORD);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32.dll ")]
        internal static extern IntPtr GetStdHandle(int nStdHandle);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        internal static extern int GetConsoleTitle(String sb, int capacity);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("user32.dll", EntryPoint = "UpdateWindow")]
        internal static extern int UpdateWindow(IntPtr hwnd);

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("user32.dll")]
        internal static extern IntPtr FindWindow(String sClassName, String sAppName);

        public static void OpenConsole()
        {
            var consoleTitle = "> Debug Console";
            AllocConsole();


            Console.BackgroundColor = ConsoleColor.Black;
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WindowWidth = 80;
            Console.CursorVisible = false;
            Console.Title = consoleTitle;
            Console.WriteLine("DEBUG CONSOLE WAIT OUTPUTING...{0} {1}\n", DateTime.Now.ToLongTimeString());

            try
            {
                //这里是改控制台字体大小的,可能会导致异常,在我这个项目中我懒得弄了,如果需要的的话把注释去掉就行了
                //IntPtr hwnd = FindWindow(null, consoleTitle);
                //IntPtr hOut = GetStdHandle(-11);

                //const uint MAX_FONTS = 40;
                //uint num_fonts = GetNumberOfConsoleFonts();
                //if (num_fonts > MAX_FONTS) num_fonts = MAX_FONTS;
                //CONSOLE_FONT[] fonts = new CONSOLE_FONT[MAX_FONTS];
                //GetConsoleFontInfo(hOut, 0, num_fonts, fonts);
                //for (var n = 7; n < num_fonts; ++n)
                //{
                //    //fonts[n].dim = GetConsoleFontSize(hOut, fonts[n].index);
                //    //if (fonts[n].dim.X == 106 && fonts[n].dim.Y == 33)
                //    //{
                //        SetConsoleFont(hOut, fonts[n].index);
                //        UpdateWindow(hwnd);
                //        return;
                //    //}
                //}
            }
            catch
            {

            }
        }

        public static void Log(String format, params object[] args)
        {
            Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] " + format, args);
        }
        public static void Log(Object arg)
        {
            Console.WriteLine(arg);
        }
    }
}
复制代码

在程序启动时,可以用ConsoleEx.OpenConsole()打开控制台,用ConsoleEx.Log(…..)或者干脆用Console.WriteLine进行输出就可以了。

WPF的TextBox产生内存泄露的情况

前段时间参与了一个WPF编写的项目,在该项目中有这样一个场景:在程序运行过程中需要动态地产生大量文本信息,并追加WPF界面上的一个TextBox的Text中进行显示。编写完之后,运行该项目的程序,发现在产生大量信息之后,发现系统变慢了,打开任务管理器才发现,该项目的程序占用了将近1.5G的内存(天啊!!!这不是一般的耗内存啊!!!)。后来通过查资料和探索才发现了WPF的TextBox在追加Text显示文本时会造成内存泄露。下面通过一个小Demo程序来展示一下这个内存泄露。

我的Demo程序很简单,就是在界面上显示一个TextBox和一个Button,点击Button后就从0到9999进行for循环并将这些数字追加的TextBox的Text中进行显示。代码如下,

<window x:Class="TextBoxMemoryLeak.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="测试TextBox内存泄露" Height="350" Width="525"
        WindowStartupLocation="CenterScreen">
    <grid Margin="5">
        </grid><grid .RowDefinitions>
            <rowdefinition Height="*"></rowdefinition>
            <rowdefinition Height="35"></rowdefinition>
        </grid>
        <dockpanel Grid.Row="0">
            <textbox Name="tbOutput" IsReadOnly="True" VerticalScrollBarVisibility="Auto"></textbox>
        </dockpanel>
        <stackpanel Grid.Row="1"
                    FlowDirection="RightToLeft"
                    Orientation="Horizontal">
            <button Name="btnStart" Content="开 始" Margin="5,4,5,4" Width="65" Click="btnStart_Click"></button>
        </stackpanel>
    
</window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TextBoxMemoryLeak
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            this.btnStart.IsEnabled = false;
            this.tbOutput.Text = "";

            for (int i = 0; i < 10000; i++)
            {
                //使用此语句进行Textbox的追加会造成内存泄露
                //this.tbOutput.Text += string.Format("{0}\n", i);

                //使用此语句进行Textbox的追加可避免内存泄露
                this.tbOutput.AppendText(string.Format("{0}\n", i));
            }

            this.btnStart.IsEnabled = true;
        }
    }
}

界面如下所示:

内存泄露情况

最初我们采用的是TextBox的Text追加方式如下

this.tbOutput.Text += string.Format("{0}\n", i);

构建,启动调试后,我们查看任务管理器,此时所占内存只有16M,

点击【开始】按钮之后,等到从0输出到9999之后,我们再查看任务管理器,发现此时所占的内存飙到了600+M,

若此时再点击【开始】按钮,等循环结束,发现所占内存飙到了900+M,

再点击【开始】按钮的话,就要发生OutOfMemory异常的。当我们将循环改为从0到19999时,第一次点击【开始】按钮,我的机器就发生OutOfMemory异常了。

避免内存泄露的情况

将TextBox的Text追加方式改为下面语句

this.tbOutput.AppendText(string.Format("{0}\n", i));

构建,启动调试,然后点击界面的【开始】按钮,等循环结束,我们查看任务管理器,测试Demo程序只占了29M内存(此时是从0到19999的循环)。

 

 

 

TextBox存在内存泄露的可能

背景

-WPF桌面程序中,增加了一个TextBox控件,用于显示输出的日志信息,日志信息量很大(具体数值未统计)

XAML 代码
 <TextBox Text="{Binding Message}" TextWrapping="Wrap" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" TextChanged="TextBox_TextChanged" />

CS 代码
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (!(sender is TextBox)) return;
        var tb = (TextBox)sender;
        if (tb.Text.Length > 30000) //textbox maxlength = 32767
      {
        tb.Text = "Auto Clear Histroy Information\n\r";
      }
       tb.Select(tb.Text.Length, 0);
    }

问题

运行一段时间后操作系统弹出信息,称“XXX程序出问题了”(截图后补吧)。在增加日志显示功能前,此异常未出现。

解决

参考:Google搜索到的信息
怀疑是日志信息太多,造成程序内存泄露。TextBox控件支持回退功能(Undo),但是回退功能需要占用更多内存。

设置 
     UndoLimit="0"  或者 IsUndoEnabled="False"
 可关闭Undo功能