原来没有考虑到+/-输入的情况,已修改

由于项目的需要,我们在某些场合下需要对输入做限制,例如金额,需要靠右对齐,需要显示货币符号,需要显示分隔符号等等。相信大家都碰到过这种需求。上网找这种控件也很难找,要么就是收费的,要么就是不太符合国人的习惯。例如,原来用过一个第三方NetAdvantage控件,但是它是严格按照货币的格式化方式来格式化的,不适合国人的金额格式习惯。因为国人的金额格式习惯是类似于 $#,###,###.##,但是某些币种,小数点是',',分组符号是'.',是反过来的,很容易让人看错;而某些币种的输入方式是从右往左,就更不适应了。

因此,自己写了一个金额输入控件CurrencyEditor,可以根据不同的语言显示不同的货币符号和限制小数位数。没有直接用.Net的格式化decimal,而是自己调用.Net格式化之后,再处理为“货币符号+金额值”的形式。

CurrencyEditor继承自TextBox,要点是在OnTextChanged中去解析文本是否合法,并且将文本格式化,然后设置光标的位置。其实TextBox还有一个TextInput事件,但是在这个事件中处理的问题在于,复制粘贴操作不会触发TextInput,会导致用户可以用复制粘贴的方式录入非法文本。其次,就是在OnSelectionChanged中去检查光标的位置,要保证用户录入的舒适性。在OnPreviewKeyDown中处理一些非法录入,和控制光标位置,例如按下'.'直接跳到小数点之后,同时控制用户通过DEL和Backspace不会去删除掉货币符号和小数点。详情还是见代码吧,注释很详尽。

另外一个控件是限制输入的FieldTextBox,限制有两方面,一个是输入的内容,有Text和Digit两种。Text可以任意输入,Digit只能输入数字(例如,在输入卡号的时候很有用)。另一个是限制输入的字节长度,这个限制在输入一个报文中,按照字节有长度限制的字段时很有用。WPF的TextBox虽然有一个MaxLength,但是限制的是字符长度。例如,“我”是两个字节,却只是一个字符。FieldTextBox的处理逻辑与CurrencyEditor类似,但是要稍微简单一点。

附代码,跟大家分享一下:)

http://files.cnblogs.com/RMay/WpfInputs.zip

posted @ 2011-04-03 09:46 RMay 阅读(1217) 评论(2) 编辑

感谢 rgqancy 指出的Bug,已经修正

先给个效果图:

使用时的代码:

 

代码
<l:GridLineDecorator>
<ListView ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}"/>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
</GridView>
</ListView.View>
</ListView>
</l:GridLineDecorator>

 

 

------------------------正文-------------------------------

经常看见有人问在使用WPF的ListView的时候,怎样能够有网格线的效果。例如http://www.bbniu.com/forum/thread-1090-1-1.html

对这个问题,首先能想到的解决办法是,在GridViewColumn的CellTemplate中,放上一个Border,然后设置Border的BorderBrush和BorderThickness。例如:

<GridViewColumn.CellTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="1" UseLayoutRounding="True">
<TextBlock Text="{Binding Id}"/>
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>

 

但是,很快你会发现,Border不能随着列宽的变化而变化,就像这样:

而且,即使将ListView的HorizontalContentAlignment置为Stretch,也不能起到作用。必须在ListViewItem上设置HorizontalContentAlignment="True"。因此,必须添加一个ListViewItem的样式,统一指定:

 

<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>

但问题还是没有解决,因为Border不能填满整个Cell,就像这样:

于是,你得小心的设置各个Border的Margin,来让它们“恰好”都连在一起,看上去就像是连续的线条。也许调整Margin还不够,还得修改ListViewItem的模板;模板修改好了,发现创建这么多的Border性能又跟不上;最头大的是,每个Column都要指定一次CellTemplate,万一哪天边线的颜色要统一调整一下……

因此,这种办法固然可行,操作起来其实麻烦的要死。

 

有没有一种方式,可以直接在ListView上“画线”呢?固然,我们可以自己写一个ListView,在OnRender里面画线什么的,但理想的情况还是能够在可以不改动任何现有控件的条件下,实现这个画网格的功能。同时,这个网格线的颜色可以随意调整就更好了。

因此,总的要求如下:

1、可以画网格

2、不用改动ListView,或者自己写ListView

3、可以调整网格的颜色

 

如果对设计模式熟悉的话,“不改动现有代码,增加新的功能”,应该马上能够想到装饰器模式。其实,WPF中本身就有Decorator这个控件,而常用的Border就是一个Decorator,可以帮助控件画背景色,画边线等等。

因此,如果能够有这么一个Decorator,把ListView往里面一放,就能有画线的功能,岂不快哉?不过,这里我并不打算直接继承Decorator来修改,因为WPF提供的Decorator是针对所有UIElment的,而我们只想针对ListView。

GridLineDecorator直接继承自FrameworkElement,并且通过重载VisualChild和LogicalChild相关的代码来显示其包装的ListView。 

 

GridLineDecorator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;

namespace ListViewWithLines
{
[ContentProperty(
"Target")]
public class GridLineDecorator : FrameworkElement
{
private ListView _target;
private DrawingVisual _gridLinesVisual = new DrawingVisual();
private GridViewHeaderRowPresenter _headerRowPresenter = null;

public GridLineDecorator()
{
this.AddVisualChild(_gridLinesVisual);
this.AddHandler(ScrollViewer.ScrollChangedEvent, new RoutedEventHandler(OnScrollChanged));
}

#region GridLineBrush

/// <summary>
/// GridLineBrush Dependency Property
/// </summary>
public static readonly DependencyProperty GridLineBrushProperty =
DependencyProperty.Register(
"GridLineBrush", typeof(Brush), typeof(GridLineDecorator),
new FrameworkPropertyMetadata(Brushes.LightGray,
new PropertyChangedCallback(OnGridLineBrushChanged)));

/// <summary>
/// Gets or sets the GridLineBrush property. This dependency property
/// indicates ....
/// </summary>
public Brush GridLineBrush
{
get { return (Brush)GetValue(GridLineBrushProperty); }
set { SetValue(GridLineBrushProperty, value); }
}

/// <summary>
/// Handles changes to the GridLineBrush property.
/// </summary>
private static void OnGridLineBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((GridLineDecorator)d).OnGridLineBrushChanged(e);
}

/// <summary>
/// Provides derived classes an opportunity to handle changes to the GridLineBrush property.
/// </summary>
protected virtual void OnGridLineBrushChanged(DependencyPropertyChangedEventArgs e)
{
DrawGridLines();
}

#endregion

#region Target

public ListView Target
{
get { return _target; }
set
{
if (_target != value)
{
if (_target != null) Detach();
RemoveVisualChild(_target);
RemoveLogicalChild(_target);

_target
= value;

AddVisualChild(_target);
AddLogicalChild(_target);
if (_target != null) Attach();

InvalidateMeasure();
}
}
}

private void GetGridViewHeaderPresenter()
{
if (Target == null)
{
_headerRowPresenter
= null;
return;
}
_headerRowPresenter
= Target.GetDesendentChild<GridViewHeaderRowPresenter>();
}

#endregion

#region DrawGridLines

private void DrawGridLines()
{
if (Target == null) return;
if (_headerRowPresenter == null) return;

var itemCount
= Target.Items.Count;
if (itemCount == 0) return;

var gridView
= Target.View as GridView;
if (gridView == null) return;

// 获取drawingContext
var drawingContext = _gridLinesVisual.RenderOpen();
var startPoint
= new Point(0, 0);
var totalHeight
= 0.0;

// 为了对齐到像素的计算参数,否则就会看到有些线是模糊的
var dpiFactor = this.GetDpiFactor();
var pen
= new Pen(this.GridLineBrush, 1 * dpiFactor);
var halfPenWidth
= pen.Thickness / 2;
var guidelines
= new GuidelineSet();

// 画横线
for (int i = 0; i < itemCount; i++)
{
var item
= Target.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem;
if (item != null)
{
var renderSize
= item.RenderSize;
var offset
= item.TranslatePoint(startPoint, this);

var hLineX1
= offset.X;
var hLineX2
= offset.X + renderSize.Width;
var hLineY
= offset.Y + renderSize.Height;

// 加入参考线,对齐到像素
guidelines.GuidelinesY.Add(hLineY + halfPenWidth);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawLine(pen,
new Point(hLineX1, hLineY), new Point(hLineX2, hLineY));
drawingContext.Pop();

// 计算竖线总高度
totalHeight += renderSize.Height;
}
}

// 画竖线
var columns = gridView.Columns;
var headerOffset
= _headerRowPresenter.TranslatePoint(startPoint, this);
var headerSize
= _headerRowPresenter.RenderSize;

var vLineX
= headerOffset.X;
var vLineY1
= headerOffset.Y + headerSize.Height;

foreach (var column in columns)
{
var columnWidth
= column.GetColumnWidth();
vLineX
+= columnWidth;

// 加入参考线,对齐到像素
guidelines.GuidelinesX.Add(vLineX + halfPenWidth);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawLine(pen,
new Point(vLineX, vLineY1), new Point(vLineX, totalHeight));
drawingContext.Pop();
}

drawingContext.Close();
}

#endregion

#region Overrides to show Target and grid lines

protected override int VisualChildrenCount
{
get { return Target == null ? 1 : 2; }
}

protected override System.Collections.IEnumerator LogicalChildren
{
get { yield return Target; }
}

protected override Visual GetVisualChild(int index)
{
if (index == 0) return _target;
if (index == 1) return _gridLinesVisual;
throw new IndexOutOfRangeException(string.Format("Index of visual child '{0}' is out of range", index));
}

protected override Size MeasureOverride(Size availableSize)
{
if (Target != null)
{
Target.Measure(availableSize);
return Target.DesiredSize;
}

return base.MeasureOverride(availableSize);
}

protected override Size ArrangeOverride(Size finalSize)
{
if (Target != null)
Target.Arrange(
new Rect(new Point(0, 0), finalSize));

return base.ArrangeOverride(finalSize);
}

#endregion

#region Handle Events

private void Attach()
{
_target.Loaded
+= OnTargetLoaded;
_target.Unloaded
+= OnTargetUnloaded;
}

private void Detach()
{
_target.Loaded
-= OnTargetLoaded;
_target.Unloaded
-= OnTargetUnloaded;
}

private void OnTargetLoaded(object sender, RoutedEventArgs e)
{
if (_headerRowPresenter == null)
GetGridViewHeaderPresenter();
DrawGridLines();
}

private void OnTargetUnloaded(object sender, RoutedEventArgs e)
{
DrawGridLines();
}

private void OnScrollChanged(object sender, RoutedEventArgs e)
{
DrawGridLines();
}

#endregion
}
}

 

其中,Target是一个属性,类型是ListView,而有一个_guidLinesVisual,则是用于绘制网格的DrawingVisual。有人可能会问,为什么不直接重载OnRender方法,在里面画线呢?

 

理由是,重载OnRender方法画线,当ListView设置了背景后,会将我们画的线盖住。这是因为控件的背景是在模板中放了一个Border来绘制的,Border也是在OnRender中绘制的,它后绘制,我们的先绘制,会将我们画的线给盖住。同时,你会发现,当ListView的Column改变大小的时候,并不会引起GridLineDecorator重绘,所以网格线无法同步变化。

其实,GridLineDecorator里面的GetVisualChild重载也非常讲究:

代码
protected override Visual GetVisualChild(int index)
{
if (index == 0) return _target;
if (index == 1) return _gridLinesVisual;
throw new IndexOutOfRangeException(string.Format("Index of visual child '{0}' is out of range", index));
}

首先返回的是ListView,接着才是_gridLinesVisual。
不过,即使是使用DrawingVisual,也会有Column宽度改变无法通知重绘的问题。解决这个问题有好几个思路:
1、监听一下GridViewColumn的宽度变化
2、监听CompositionTarget.Rendering事件
第一个办法,不可行,因为GridViewColumn的宽度变化事件你找不到,第二办法是可行,不过效率嘛……

在经过一番研究之后,终于找到了一个可行的办法,监听ScrollViewer的ScrollChanged事件,因为ListView内部是放置了两个ScrollViewer,一个用于显示Header,一个用于显示Items。当Column的宽度变化时,会触发ScrollViewer的ScrollChanged事件。

 

因此,在构造函数里面:

代码
public GridLineDecorator()
{
this.AddVisualChild(_gridLinesVisual);
this.AddHandler(ScrollViewer.ScrollChangedEvent, new RoutedEventHandler(OnScrollChanged));
}

画线的逻辑,主要就是遍历所有的Container(其实是ListViewItem),计算其相对于GridLineDecorator的位移,算出横线和纵线的坐标和长度,画线。代码比较多,大家可以下载以后自己看。

细心的童鞋可能会发现,有时候底部的线条在ListViewItem显示不完整时,没有画到最下端,这是由于ListView做了Virtualize处理。大家可以设置VirtualizingStackPanel.IsVirtualizing="False"来强制绘制。

附代码:http://files.cnblogs.com/RMay/ListViewWithLines.zip

posted @ 2010-12-27 16:59 RMay 阅读(1848) 评论(9) 编辑

先发表在棒棒牛了,转过来吧。

传送门:http://www.bbniu.com/forum/viewthread.php?tid=752&page=1&extra=#pid6692

WPF 4.0的DatePicker在通过键盘录入日期的时候是非常让人郁闷的。必须按照日期的格式来完整输入
例如,比如输入“2010/10/10”才能识别。而实际上在一些要求快速录入的场合,用户更希望直接敲20101010就行了。
遗憾的是,DatePicker没有一个属性可以设置说录入的格式是yyyyMMdd这种的。

实际上,仔细看一下DatePicker控件,它有一个DateValidationError事件,当输入的文本无法识别为日期时,就会触发该事件。我们可以利用这个事件来做一些事情。

为了方便使用,我们可以封装一个附加属性,在需要快速录入的地方Attach一下就好了。

public static readonly DependencyProperty EnableFastInputProperty =
         DependencyProperty.RegisterAttached("EnableFastInput", typeof(bool), typeof(DatePickerHelper),
                new FrameworkPropertyMetadata((bool)false,
                new PropertyChangedCallback(OnEnableFastInputChanged)));

public static bool GetEnableFastInput(DependencyObject d)
{
       return (bool)d.GetValue(EnableFastInputProperty);
}

public static void SetEnableFastInput(DependencyObject d, bool value)
{
       d.SetValue(EnableFastInputProperty, value);
}

这样,我们就给一个DatePickerHelper类型注册了一个附加属性,叫做EnableFastInput。
在这个属性的PropertyChanged事件处理函数中,我们监听DatePicker的DateValidationError事件
private static void OnEnableFastInputChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
      var datePicker = d as DatePicker;
      if (datePicker != null)
      {
            if ((bool)e.NewValue)
            {
                  datePicker.DateValidationError += DatePickerOnDateValidationError;
            }
            else
            {
                  datePicker.DateValidationError -= DatePickerOnDateValidationError;
            }
      }
}

在事件处理中,我们尝试着解析文本,并且设置日期:
private static void DatePickerOnDateValidationError(object sender, DatePickerDateValidationErrorEventArgs e)
{
       var datePicker = sender as DatePicker;
       if (datePicker != null)
       {
             var text = e.Text;
             DateTime dateTime;
             if (DateTime.TryParseExact(text, "yyyyMMdd", CultureInfo.CurrentUICulture, DateTimeStyles.None, out dateTime))
             {
                   datePicker.SelectedDate = dateTime;
             }
       }
}
在Xaml中使用时:
<DatePicker l:DatePickerHelper.EnabledFastInput="True"/>

这样,DatePicker就支持直接输入yyyyMMdd格式的日期了。
当然,这里的日期格式是写死的,可以考虑封装成另外一个DatePickerHelper.InputDateFormat属性之类的,更加灵活

 

【效果图】

【代码】

/Files/RMay/WpfDatePicker.zip

posted @ 2010-05-27 17:35 RMay 阅读(733) 评论(1) 编辑

WPF中自带一个WebBrowser控件,当我们使用它打开一个网页,例如百度,然后点击它其中的链接时,如果这个链接是会弹出一个新窗口的,那么它会生生的弹出一个IE窗口来,而不是在内部跳到该链接。

如果使用Winform的WebBrowser控件,我们可以监听它的NewWindow事件,在这个事件中做一些处理,例如,在新建一个Tab来打开,或者控制它在当前WebBrowser中跳转。很不幸的是,WPF的WebBrowser没有这个事件。

说到底,Winform的WB或者是WPF的WB都是在调用IE的一个控件,因此,Winform能加上的,我们WPF一定也有办法加上。如此,那我们就请出神器Reflector,研究一把。

首先,我们打开Winform的WebBrowser,找到触发NewWindow事件的代码:

    protected virtual void OnNewWindow(CancelEventArgs e)
    {
        
if (this.NewWindow != null)
        {
            
this.NewWindow(this, e);
        }
    }

它是在OnNewWindow方法中触发的。那么,是谁调用了这个OnNewWindow呢?接着搜索,最后在一个叫WebBrowserEvent的类里面发现这么一段:

public void NewWindow2(ref object ppDisp, ref bool cancel)
{
     CancelEventArgs e 
= new CancelEventArgs();
     
this.parent.OnNewWindow(e);
     cancel 
= e.Cancel;
}

我们接着搜NewWindow2,却发现没有地方显式地调用它了。既然从方法入手没找到,那我们就来研究一下定义这个方法的WebBrowserEvent,看看是谁在使用它。
仔细搜索一遍,最后发现在WebBrowser的CreateSink方法中有这么一段:

代码
protected override void CreateSink()
{
    
object activeXInstance = base.activeXInstance;
    
if (activeXInstance != null)
    {
        
this.webBrowserEvent = new WebBrowserEvent(this);
        
this.webBrowserEvent.AllowNavigation = this.AllowNavigation;
        
this.cookie = new AxHost.ConnectionPointCookie(activeXInstance, this.webBrowserEvent, typeof(UnsafeNativeMethods.DWebBrowserEvents2));
    }
}

注意这句话:

this.cookie = new AxHost.ConnectionPointCookie(activeXInstance, this.webBrowserEvent, typeof(UnsafeNativeMethods.DWebBrowserEvents2));

很显然,这句话是关键。AxHost.ConnectionPointCookie类的作用是:“将一个ActiveX 控件连接到处理该控件的事件的客户端”。

上面的调用中有一个很奇怪的类型:DWebBrowserEvents2,熟悉COM的童鞋应该马上能想到,这其实是一个COM类型的定义。

 

代码
[ComImport, TypeLibType(TypeLibTypeFlags.FHidden), InterfaceType(ComInterfaceType.InterfaceIsIDispatch), Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D")]
public interface DWebBrowserEvents2
{
     ......
}

实际上,我们再去看WebBrowserEvent的定义,它恰恰是实现了这个接口的。

[ClassInterface(ClassInterfaceType.None)]
private class WebBrowserEvent : StandardOleMarshalObject, UnsafeNativeMethods.DWebBrowserEvents2
{
    ......
}

因此,上面这句话不难理解,就是定义一个实现了特定COM接口的类型,让浏览器控件的事件能够转发到这个类型实例去处理。因此,NewWindow2其实是浏览器控件去调用的。

Winform的WebBrowser我们搞清楚了,下面我们来看WPF的。其实,打开WPF的WebBrowser代码之后,我们会发现它跟Winform的WebBrowser机制是一样的。一个似曾相识的CreateSink方法映入眼中:

代码
[SecurityTreatAsSafe, SecurityCritical]
internal override void CreateSink()
{
    
this._cookie = new ConnectionPointCookie(this._axIWebBrowser2, this._hostingAdaptor.CreateEventSink(), typeof(UnsafeNativeMethods.DWebBrowserEvents2));
}

这儿也有一个ConnectionPointCookie,但是它的访问权限是internal的:(
第二个参数,_hostingAdapter.CreateEventSink返回的是什么呢:

代码
[SecurityCritical]
internal virtual object CreateEventSink()
{
    
return new WebBrowserEvent(this._webBrowser);
}

[ClassInterface(ClassInterfaceType.None)]
internal class WebBrowserEvent : InternalDispatchObject<UnsafeNativeMethods.DWebBrowserEvents2>, UnsafeNativeMethods.DWebBrowserEvents2
{
    ......
}

仍然是一个WebBrowserEvent!悲剧的是,这个WPF的WebBrowserEvent,并没有触发NewWindowEvent:

public void NewWindow2(ref object ppDisp, ref bool cancel)
{
}

现在知道为什么WPF的WB控件没有NewWindow事件了吧?微软的童鞋压根儿就没写!

既然微软的童鞋不写,那我们就自己折腾一把,反正原理已经搞清楚了。

首先,我们也得定义一个DWebBrowserEvents2接口,这个我们直接通过Reflector复制一份就好了。代码就不贴上来了。

接着,我们再仿造一个WebBrowserEvent,关键是要触发NewWindow事件:

代码
public partial class WebBrowserHelper
    {
        
private class WebBrowserEvent : StandardOleMarshalObject, DWebBrowserEvents2
        {
            
private WebBrowserHelper _helperInstance = null;

            
public WebBrowserEvent(WebBrowserHelper helperInstance)
            {
                _helperInstance = helperInstance;
            }
            ......
            
            
public void NewWindow2(ref object pDisp, ref bool cancel)
            {
                _helperInstance.OnNewWindow(ref cancel);
            }
            ......
        }
    }

 

最后,我们需要仿造Framework中的代码,也来CreateSink一把(我承认,用了反射来取WebBrowser内部的东东,谁让这些类型都是internal的呢):

代码
private void Attach()
{
    var axIWebBrowser2 = _webBrowser.ReflectGetProperty("AxIWebBrowser2");
    var webBrowserEvent = new WebBrowserEvent(this);
    var cookieType = typeof(WebBrowser).Assembly.GetType("MS.Internal.Controls.ConnectionPointCookie");
    _cookie = Activator.CreateInstance(
        cookieType,
        ReflectionService.BindingFlags,
        
null,
        
new[] { axIWebBrowser2, webBrowserEvent, typeof(DWebBrowserEvents2) },
        CultureInfo.CurrentUICulture);
}


最后的使用:

var webBrowserHelper = new WebBrowserHelper(webBrowser);
......
webBrowserHelper.NewWindow 
+= WebBrowserOnNewWindow;


【效果图】

初始网页:


点击一个链接,默认情况下,将是弹出一个IE窗口,现在是在新的Tab中打开:

 

【示例代码】

(新建按钮点击后,请输入完整的网址,例如:http://www.sina.com)

/Files/RMay/WpfWebBrowser.zip

 



posted @ 2010-05-27 15:34 RMay 阅读(3164) 评论(17) 编辑
还存在一些问题,再研究一下
1、ComponentDispatcher其实可以不用
2、new一个DispatcherFrame其实是把一个消息循环(姑且称作嵌套消息循环)当做一个DispatchFrame来处理,在这个消息循环结束之前,原来的代码就是阻塞的
3、正是因为第二个原因,如果再次弹出一个窗口,将是在前一个嵌套消息循环中,再次执行2,也就会导致第一个窗口关闭,并不会立即执行后面的代码。

【场景描述】

      某些时候可能会有这种需求,一个用户界面里面分为好多个功能区域。这些功能区域有时候会有一些“模态”的弹出窗口的交互,这些弹出窗口需要:
1、只影响当前区域。即鼠标或者键盘无法操作当前区域,而其他区域不受影响。比如说,有好多个选项卡页面,每个选项卡页面弹出的窗口只影响当前选项卡,而不影响其他的选项卡。
2、窗口未关闭之前,后面的代码不执行,即“阻塞”。

【问题分析】

WPF中的窗口的弹出提供了两个方法:Show和ShowDialog。其中,Show方式是一个非“阻塞”的方法,即:调用完Show之后立即返回,后面的代码将立即被执行,但是这个方法不会挡住整个窗口。而ShowDialog方法是一个“阻塞”的方法,即调用完ShowDialog之后需要等窗口关闭代码才会继续执行。然而,ShowDialog会挡住整个窗口,而不是指定的区域。
综合这个两个方法考虑,其实,对于场景中的第一条,我们可以考虑通过调用Show让窗口弹出之后,即将该区域置为IsEnabled=false来模拟“模态”的窗口,这个倒不是很难。问题在于第二条,如何让窗口“阻塞”住当前代码的继续执行呢?即:
var win = new MyDialog();
win.Show();
// 关闭后才要执行的代码
1、用一个while死循环,直到关闭才跳出。如果采用这种方式,那我们不得不好好考虑一下,在UI线程做死循环UI还有没有办法响应。当然,方法是有的,可以实现一个WPF版的DoEvents方法,然后在循环中调用。DoEvents相关的代码可以参见:http://www.cnblogs.com/sheva/archive/2006/08/24/485790.html
2、用异步模式来写。代码将类似于:
var win = new MyDialog();
// MyCloseCallback是一个窗口关闭时调用的回调
win.Show(MyCloseCallback);
这样确实可以满足需求,不过我们不得不忍受把一份代码拆开为两份来写的痛苦。当然,匿名方法和Lamba可以带来一些缓解,但是写起来始终不是那么舒服。
难道,我们除了使用可怕的while死循环+DoEvents,或者忍受别扭的代码之外,就没有别的办法了么?

【揭开ShowDialog的面纱】

其实,回过头再去想,为什么ShowDialog方法就可以阻塞代码的执行呢?如果我们能够知道如何阻塞代码,然后把“挡住”整个窗口的部分给去掉,不就OK了么?
好,我们请出神器Reflector打开Window的ShowDialog方法一窥究竟。
Code
核心的部分如上所示,其实ShowDialog只是置了一个状态,最主要的代码还是在Show里面。好,我们接着转战到Show
Code
前面都是在做一些检查,最终的调用原来跑到了ShowHelper
Code
关键一步看来是:
ComponentDispatcher.PushModal();
ComponentDispatcher是个什么东东?MSDN之:

Enables shared control of the message pump between Win32 and WPF in interoperation scenarios.
原来是用于操作消息循环的。
而这两句:
this._dispatcherFrame = new DispatcherFrame();
Dispatcher.PushFrame(this._dispatcherFrame);
如果有看过DoEvents的实现,就好理解了,简单的说,这里其实是让UI可以继续处理消息队列。

【实现】

有了以上准备,我们就很好处理了。
1、首先,我们通过模仿ShowHelper中的代码,来实现阻塞代码的执行
2、其次,我们通过设置IsEnabled属性来模拟“模态”的效果。

 

Code

使用的时候

Code

代码下载

/Files/RMay/TryMessageBox.zip


posted @ 2009-09-02 20:43 RMay 阅读(2012) 评论(3) 编辑
仅此纪念Michael.Jackson
posted @ 2009-06-26 20:22 RMay 阅读(96) 评论(0) 编辑
posted @ 2009-06-05 17:04 RMay 阅读(2156) 评论(10) 编辑
摘要: 控件代码已经更新,支持上下左右四个方向。VS2010工程/Files/RMay/WPF_Marquee/WpfMarquee.zip我们知道在html中有一个marquee标签,可以很方便的实现文字滚动的效果,比如如下简单的声明:<marquee loop="3" behavior="scroll">文本信息<marquee>在WPF里面,当然,我们可以用Animation...阅读全文
posted @ 2009-01-20 17:15 RMay 阅读(2019) 评论(0) 编辑
摘要: 更新:在网上找到更正规的解决方案http://thewpfblog.com/?p=61今天在群里有人问到:“怎样设置 TextBlock.ToolTip 的width,使得过长的字符串自动换行”其实ToolTip是一个object,我们可以在其中放置任何东西,所以要解决这个问题,其实很简单,只需要写如下的xaml代码:<TextBlock><TextBlo...阅读全文
posted @ 2009-01-14 15:17 RMay 阅读(2186) 评论(6) 编辑
摘要: 今天为了帮别人解决一个3D的问题,需要将自己以前做的一个Demo发过去,结果,原来能正常运行的程序现在无法运行了。调试时报出的错误是:“无法将“System.Windows.Media.Media3D.PerspectiveCamera”值分配到对象“System.Windows.Controls.Viewport3D”的属性“...阅读全文
posted @ 2008-12-10 17:39 RMay 阅读(1297) 评论(2) 编辑