首页 > 其他分享 >【WPF】INotifyPropertyChanged 的原理刨析

【WPF】INotifyPropertyChanged 的原理刨析

时间:2022-09-04 14:00:57浏览次数:56  
标签:INotifyPropertyChanged 刨析 object private public source WPF null void

1、本文转载自https://blog.csdn.net/qhwoaini/article/details/125836037

2、Wpf中的Binding

        熟悉wpf的朋友都知道wpf中大部分控件都继承自FrameworkElement,FrameworkElement中有一个方法SetBinding

 

public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding)
{
    return BindingOperations.SetBinding(this, dp, binding);
}

 

这个方法有两个参数DependencyProperty和BindingBase以及一个返回值BindingExpressionBase,我们简单看下这3个类
        (1)DependencyProperty wpf中的依赖属性 例如:TextBox.TextProperty

public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(TextBox), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnTextPropertyChanged, CoerceText, isAnimationProhibited: true, UpdateSourceTrigger.LostFocus));
 
public string Text
{
    get
    {
        return (string)GetValue(TextProperty);
    }
    set
    {
        SetValue(TextProperty, value);
    }
}

(2)BindingBase wpf中数据绑定的基类,经常使用的Binding就是BindingBase的子类,BindingBase中有一个内部的方法CreateBindingExpression,CreateBindingExpression中直接调用了虚方法CreateBindingExpressionOverride,主要代码如下

internal BindingExpressionBase CreateBindingExpression(DependencyObject targetObject, DependencyProperty targetProperty)
{
    _isSealed = true;
    return CreateBindingExpressionOverride(targetObject, targetProperty, null);
}
 
internal abstract BindingExpressionBase CreateBindingExpressionOverride(DependencyObject targetObject, DependencyProperty targetProperty, BindingExpressionBase owner);

Binding继承BindingBase重写了CreateBindingExpressionOverride

internal override BindingExpressionBase CreateBindingExpressionOverride(DependencyObject target, DependencyProperty dp, BindingExpressionBase owner)
{
    return BindingExpression.CreateBindingExpression(target, dp, this, owner);
}

可以看到CreateBindingExpressionOverride的返回值为BindingExpressionBase,等下再说这两个方法,先介绍BindingExpressionBase
(3)BindingExpressionBase 从字面意思可以理解为Binding的表达式,实际上就是存储Binding的值的关系(谁绑定了谁)
        可以看出SetBinding时将控件的依赖属性和要Binding的数据做了一个关系,即BindingExpressionBase,我们知道一个正常的通知要么用委托要么用事件将两个需要相互影响的值关联起来。下面我列举2种形式:
        1)通知方/被通知方关联目标对象

public class TestVM
{
    public event Action<string> VMChanged;
 
    private string vM;
 
    public string VM
    {
        get => vM;
        set
        {
            vM = value;
            VMChanged?.Invoke(vM);
        }
    }
}
 
public class MyButton : Button
{
    public MyButton()
    { 
            
    }
 
    public MyButton(TestVM testVM)
    {
        testVM.VMChanged += TestVM_VMChanged;
    }
 
    private void TestVM_VMChanged(string data)
    {
        this.Content = data;
    }
}

如上方代码所示,可以在通知方增加属性改变事件,在被通知方构造时订阅这个改变事件从而实现值改变,但是这样做会有一个明显的缺点TestVM和MyButton出现和耦合,MyButton需要有一个方法将TestVM传递到类内部,此时MyButton订阅了TestVM的VMChanged事件,那么假如TestVM一直没被回收,MyButton就一直有一个TestVM的引用导致MyButton无法被回收
        2)借助第3个类

public class Main
{
    private MyButton _testButton;
 
    private TestVM _testVM;
 
    public void Init()
    {
        _testButton = new MyButton();
        _testVM = new TestVM();
        _testVM.VMChanged += TestVM_VMChanged;
    }
 
    private void TestVM_VMChanged(string data)
    {
        if (_testButton != null)
            _testButton.Content = data;
    }
 
    public void DisposedTestButton()
    {
        _testButton = null;
    }
 
    public void DisposedTestTestVM()
    {
        _testVM = null;
    }
}

   如上方代码所示,借助第3个类Main来将TestVM和MyButton关联起来,这样的话TestVM和MyButton直接就没有了直接联系,我们单方面去调用DisposedTestButton或者DisposedTestTestVM都是可以将他们回收掉的,这也是我们日常开发所用到最多的处理方式,将两个需要相互或者单方向通知的对象之间的关系转移到第3个类(主页面),当然Binding也是采用这种处理方式,不过由于Binding需要更通用且需要自动回收所以做了一些额外处理 

3、INotifyPropertyChanged

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

我们日常的用法和MvvmLight中的ObservableObject的用法是一致的,下面贴出ObservableObject的部分源码以及我们的日常实现

public class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
 
    public virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
 
 
public class TestVM : INotifyPropertyChanged
{
    public event Action<string> VMChanged;
 
    private string vM;
 
    public string VM
    {
        get => vM;
        set
        {
            vM = value;
            VMChanged?.Invoke(vM);
            NotifyPropertyChanged("VM");
        }
    }
 
    #region 实现接口
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    public void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
 
    #endregion
}

   当然只是这样还不够,还需要在前台xaml中对他们进行绑定,在将MyButton_Test的上下文设置为指定的对象(TestVM),就可以实现TestVM和MyButton的相互通知了
<local:MyButton x:Name="MyButton_Test" Content="{Binding VM}"/>
        不过我们知道两个事物不可能无缘无故的联系起来,那么必定在Binding的时候做了一些处理,将两个对象关联起来了,下面看源码,由于源码较长只截取关键路径

public sealed class BindingExpression : BindingExpressionBase, IDataBindEngineClient, IWeakEventListener
{
    private BindingWorker Worker => _worker;
    
    private void CreateWorker()
    {
        Invariant.Assert(Worker == null, "duplicate call to CreateWorker");
        _worker = new ClrBindingWorker(this, base.Engine);
    }
    
    public override void UpdateTarget()
    {
        if (base.IsDetached)
        {
            throw new InvalidOperationException(SR.Get("BindingExpressionIsDetached"));
        }
 
        if (Worker != null)
        {
            Worker.RefreshValue();
        }
    }
}
 
internal class ClrBindingWorker : BindingWorker
{
    private PropertyPathWorker PW => _pathWorker;
    
    internal override void RefreshValue()
    {
        PW.RefreshValue();
    }
}
 
internal sealed class PropertyPathWorker : IWeakEventListener
{
    internal void RefreshValue()
    {
        for (int i = 1; i < _arySVS.Length; i++)
        {
            object reference = BindingExpressionBase.GetReference(_arySVS[i].item);
            if (!ItemsControl.EqualsEx(reference, RawValue(i - 1)))
            {
                UpdateSourceValueState(i - 1, null);
                return;
            }
        }
 
        UpdateSourceValueState(Length - 1, null);
    }
    
    private void UpdateSourceValueState(int k, ICollectionView collectionView)
    {
        UpdateSourceValueState(k, collectionView, BindingExpression.NullDataItem, isASubPropertyChange: false);
    }
 
    private void UpdateSourceValueState(int k, ICollectionView collectionView, object newValue, bool isASubPropertyChange)
    {
        //...省略前方代码
        for (k++; k < _arySVS.Length; k++)
        {
            isASubPropertyChange = false;
            ICollectionView collectionView2 = _arySVS[k].collectionView;
            obj = ((newValue == BindingExpression.NullDataItem) ? RawValue(k - 1) : newValue);
            newValue = BindingExpression.NullDataItem;
            if (obj == AsyncRequestPending)
            {
                _status = PropertyPathStatus.AsyncRequestPending;
                break;
            }
 
            if (!flag && obj == BindingExpressionBase.DisconnectedItem && _arySVS[k - 1].info == FrameworkElement.DataContextProperty)
            {
                flag = true;
            }
 
            ReplaceItem(k, BindingExpression.NullDataItem, obj);
            ICollectionView collectionView3 = _arySVS[k].collectionView;
            if (collectionView2 != collectionView3 && _host != null)
            {
                _host.ReplaceCurrentItem(collectionView2, collectionView3);
            }
        }
        //...省略后方代码
    }
 
    private void ReplaceItem(int k, object newO, object parent)
    {
        //...省略前方代码
        if (IsDynamic && SVI[k].type != SourceValueType.Direct)
        {
            Engine.RegisterForCacheChanges(newO, svs.info);
            PropertyPath.DowncastAccessor(svs.info, out DependencyProperty dp2, out PropertyInfo pi2, out PropertyDescriptor pd2, out DynamicObjectAccessor _);
            INotifyPropertyChanged source2;
            if (newO == BindingExpression.StaticSource)
            {
                Type type2 = (pi2 != null) ? pi2.DeclaringType : pd2?.ComponentType;
                if (type2 != null)
                {
                    StaticPropertyChangedEventManager.AddHandler(type2, OnStaticPropertyChanged, SVI[k].propertyName);
                }
            }
            else if (dp2 != null)
            {
                _dependencySourcesChanged = true;
            }
            else if ((source2 = (newO as INotifyPropertyChanged)) != null)
            {
                PropertyChangedEventManager.AddHandler(source2, OnPropertyChanged, SVI[k].propertyName);
            }
            else if (pd2 != null && newO != null)
            {
                ValueChangedEventManager.AddHandler(newO, OnValueChanged, pd2);
            }
        }
        //...省略后方代码
    }
}
 
public class PropertyChangedEventManager : WeakEventManager
{
    protected override void StartListening(object source)
    {
        INotifyPropertyChanged notifyPropertyChanged = (INotifyPropertyChanged)source;
        notifyPropertyChanged.PropertyChanged += OnPropertyChanged;
    }
    
    protected override void StopListening(object source)
    {
        INotifyPropertyChanged notifyPropertyChanged = (INotifyPropertyChanged)source;
        notifyPropertyChanged.PropertyChanged -= OnPropertyChanged;
    }
    
    public static void AddHandler(INotifyPropertyChanged source, EventHandler<PropertyChangedEventArgs> handler, string propertyName)
    {
        if (handler == null)
        {
            throw new ArgumentNullException("handler");
        }
 
        CurrentManager.PrivateAddHandler(source, handler, propertyName);
    }
        
    private void PrivateAddHandler(INotifyPropertyChanged source, EventHandler<PropertyChangedEventArgs> handler, string propertyName)
    {
        AddListener(source, propertyName, null, handler);
    }
    
    private void AddListener(INotifyPropertyChanged source, string propertyName, IWeakEventListener listener, EventHandler<PropertyChangedEventArgs> handler)
    {
        using (base.WriteLock)
        {
            //...省略前方代码
            HybridDictionary hybridDictionary = (HybridDictionary)base[source];
            if (hybridDictionary == null)
            {
                hybridDictionary = (HybridDictionary)(base[source] = new HybridDictionary(caseInsensitive: true));
                StartListening(source);
            }
            //...省略后方代码
        }
    }
 
    private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        //...省略前方代码
        base.DeliverEventToList(sender, args, listenerList);
        //...省略后方代码
    }
}
 
public abstract class WeakEventManager : DispatcherObject
{
    protected void DeliverEventToList(object sender, EventArgs args, WeakEventManager.ListenerList list)
    {
        bool flag = list.DeliverEvent(sender, args, base.GetType());
        if (flag)
        {
            this.ScheduleCleanup();
        }
    }        
 
    public override bool DeliverEvent(object sender, EventArgs e, Type managerType)
    {
        TEventArgs e2 = (TEventArgs)((object)e);
        bool flag = false;
        int i = 0;
        int count = base.Count;
        while (i < count)
        {
            WeakEventManager.Listener listener = base.GetListener(i);
            if (listener.Target != null)
            {
                //eventHandler就是PropertyPathWorker中订阅INotifyPropertyChanged.PropertyChanged事件的方法
                EventHandler<TEventArgs> eventHandler = (EventHandler<TEventArgs>)listener.Handler;
                if (eventHandler != null)
                {
                    eventHandler(sender, e2);
                }
                else
                {
                    flag |= base.DeliverEvent(ref listener, sender, e, managerType);
                }
            }
            else
            {
                flag = true;
            }
            i++;
        }
        return flag;
    }
}
 

     可以看到在BindingExpression在调用UpdateTarget时最终会调用PropertyChangedEventManager的StartListening订阅INotifyPropertyChanged的PropertyChanged事件,至此两个绑定的属性产生了联系,只要我们在模型里面调用PropertyChanged就可是通知到对应需要改变的对象的属性,需要注意的是这里分别有一个重要的类和接口(WeakEventManager和IWeakEventListener),我们可以看到WeakEventManager充当的角色就是上面的Main(第三个类)将两个类关系起来,下面我们实现自己的通知时需要用到,由于具体改变属性的代码量比较大并且控件和UI具体怎么交互也不太了解,之后学习DependencyObject和DependencyProperty再详细介绍
        下面是简单的一个调用关系图

 

 

4.根据.Net的实现逻辑实现通知

       说了这么多我们仿照着.net的实现逻辑自己实现一份,下面先贴代码

public interface IMyWeakEventTest
{
    event Action<object, string> PropertyChanged;
}

第一个接口IMyWeakEventTest对应INotifyPropertyChanged

public class MyWeakEventBindingExpTest : IWeakEventListener
{
    public MyWeakEventBindingExpTest(DependencyObject dobj, DependencyProperty dp, object obj, string sourceFiled)
    {
        Target = new WeakReference<DependencyObject>(dobj);
        Source = new WeakReference<object>(obj);
        DProperty = dp;
        SourceFiled = sourceFiled;
    }
 
    public DependencyProperty DProperty { get; set; }
 
    public WeakReference<DependencyObject> Target { get; set; }
 
    public WeakReference<object> Source { get; set; }
 
    public string SourceFiled { get; set; }
 
    public object RSource
    {
        get
        {
            if (Source.TryGetTarget(out var source))
                return source;
            return null;
        }
    }
 
    public DependencyObject RTarget
    {
        get
        {
            if (Target.TryGetTarget(out var target))
                return (DependencyObject)target;
            return null;
        }
    }
 
    public void UpdateTarget()
    {
        this.RTarget.SetValue(this.DProperty, this.RSource.GetType().GetProperty(SourceFiled)?.GetValue(this.RSource));
    }
 
    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
        var myWeakEventArgsTest = e as MyWeakEventArgsTest;
        if (myWeakEventArgsTest == null)
            return true;
 
        if (myWeakEventArgsTest.Value != SourceFiled)
            return true;
 
        this.UpdateTarget();
        return true;
    }
}

   第二个MyWeakEventBindingExpTest对应BindingExpression和PropertyPathWorker,由于现在只说原理所以将BindingExpression和PropertyPathWorker的功能合并了,这里可以看到Source(数据源)和Target(目标)我们用了WeakReference(弱引用类)目的是为了内存的自动释放

public class MyWeakEventManagerTest : WeakEventManager
{
    protected override void StartListening(object source)
    {
        var weTest = source as IMyWeakEventTest;
        if (weTest != null)
            weTest.PropertyChanged += WeTest_PropertyChanged;
    }
 
    private void WeTest_PropertyChanged(object arg1, string arg2)
    {
        base.DeliverEvent(arg1, new MyWeakEventArgsTest() { Value = arg2 });
    }
 
    protected override void StopListening(object source)
    {
        var weTest = source as IMyWeakEventTest;
        if (weTest != null)
            weTest.PropertyChanged -= WeTest_PropertyChanged;
    }
 
    public void AddLinstener(object source, IWeakEventListener eventListener)
    {
        var myBindingExp = eventListener as MyWeakEventBindingExpTest;
        MyWeakEventTestExtension.SetWeakListener(myBindingExp.RTarget, myBindingExp);
        myBindingExp.UpdateTarget();
        this.ProtectedAddListener(source, eventListener);
    }
 
    public void RemoveLinstener(object source, IWeakEventListener eventListener)
    {
        this.ProtectedRemoveListener(source, eventListener);
    }
}

第三个MyWeakEventManagerTest对应PropertyChangedEventMana

public class MyWeakEventTestExtension
{
    public static readonly DependencyProperty WeakListenerProperty = DependencyProperty.RegisterAttached("WeakListener", typeof(IWeakEventListener), typeof(MyWeakEventTestExtension), new PropertyMetadata(null));
 
    public static void SetWeakListener(DependencyObject element, IWeakEventListener value) => element.SetValue(WeakListenerProperty, value);
 
    public static IWeakEventListener GetWeakListener(DependencyObject element) => (IWeakEventListener)element.GetValue(WeakListenerProperty);
}

 第四个是一个扩展类,主要是为了在Target(即控件)存储IWeakEventListener即MyWeakEventBindingExpTest避免MyWeakEventBindingExpTest被回收

        上面代码和框架提供的结构基本一致同样用到了WeakEventManager和IWeakEventListene先看类名WeakEvent(弱事件)Manager(管理员),我们都知道弱引用它会在系统需要内存时被回收,WeakEventManager也是同样的,WeakEventManager内部有一个虚方法Purge(清除),会对无用的订阅进行释放,IWeakEventListene就是为WeakEventManager服务的

protected virtual bool Purge(object source, object data, bool purgeAll)
{
    bool result = false;
    bool flag = purgeAll || source == null;
    if (!flag)
    {
        ListenerList list = (ListenerList)data;
        if (ListenerList.PrepareForWriting(ref list) && source != null)
        {
            Table[this, source] = list;
        }
 
        if (list.Purge())
        {
            result = true;
        }
 
        flag = list.IsEmpty;
    }
 
    if (flag && source != null)
    {
        StopListening(source);
        if (!purgeAll)
        {
            Table.Remove(this, source);
            result = true;
        }
    }
 
    return result;
}

5.测试自定义通知,内存分析

        我们先简单的搭一个测试例子

后台代码如下

public partial class Window13 : Window
{
    #region 私有成员
 
    //索引
    private int _index = 1;
 
    //通知模型列表
    private List<MyWeakEventTest> _myWeakEventTests = new List<MyWeakEventTest>();
 
    //弱引用管理
    private MyWeakEventManagerTest _myWeakEventManagerTest = new MyWeakEventManagerTest();
 
    #endregion
 
    public Window13()
    {
        InitializeComponent();
        Button_TestEdit_0.Click += Button_TestEdit_0_Click;
        Button_ClearView.Click += Button_ClearView_Click;
        Button_ClearModel.Click += Button_ClearModel_Click;
        Button_GC.Click += Button_GC_Click;
        Button_Rest.Click += Button_Rest_Click;
        reset();
    }
 
    #region 控件事件
 
    /// <summary>
    /// 重置视图
    /// </summary>
    private void Button_Rest_Click(object sender, RoutedEventArgs e)
    {
        reset();
    }
 
    /// <summary>
    /// GC
    /// </summary>
    private void Button_GC_Click(object sender, RoutedEventArgs e)
    {
        GC.Collect();
    }
 
    /// <summary>
    /// 清除模型即_myWeakEventTests
    /// </summary>
    private void Button_ClearModel_Click(object sender, RoutedEventArgs e)
    {
        _myWeakEventTests.Clear();
    }
 
    /// <summary>
    /// 清除视图即WrapPanel中的TextBox
    /// </summary>
    private void Button_ClearView_Click(object sender, RoutedEventArgs e)
    {
        WrapPanel_Default.Children.Clear();
    }
 
    /// <summary>
    /// 编辑_myWeakEventTests列表中第一个元素的WTest
    /// </summary>
    private void Button_TestEdit_0_Click(object sender, RoutedEventArgs e)
    {
        _myWeakEventTests[0].WTest = $"测试修改{_index}";
        _index++;
    }
 
    #endregion
 
    #region 辅助方法
 
    /// <summary>
    /// 重置视图即清除WrapPanel中的TextBox并重新添加
    /// </summary>
    private void reset()
    {
        _myWeakEventTests.Clear();
        WrapPanel_Default.Children.Clear();
        for (int i = 0; i < 30; i++)
        {
            var myWeakEventTest = new MyWeakEventTest() { WTest = i.ToString() };
            MyTextBox textBox = new MyTextBox() { Width = 100, Height = 30, Text = $"a{i}" };
            _myWeakEventManagerTest.AddLinstener(myWeakEventTest, new MyWeakEventBindingExpTest(textBox, MyTextBox.TextProperty, myWeakEventTest, "WTest"));
            _myWeakEventTests.Add(myWeakEventTest);
            WrapPanel_Default.Children.Add(textBox);
        }
    }
 
    #endregion
}
 
public class MyWeakEventTest : IMyWeakEventTest
{
    private string _wTest;
 
    public string WTest
    {
        get => _wTest;
        set
        {
            _wTest = value;
            PropertyChanged?.Invoke(this, nameof(WTest));
        }
    }
 
    public event Action<object, string> PropertyChanged;
}

 五个功能按钮的功能如字面意思,具体在代码注释中体现,代码创建了一个自定义的弱引用管理类(MyWeakEventManagerTest)以及一个通知模型列表(List<MyWeakEventTest>),窗口加载时向WrapPanel中增加了30个TextBox,并调用了MyWeakEventManagerTest的AddLinstener,这一步相当于SetBinding在AddLinstener时我们将MyWeakEventBindingExpTest设置到TextBox的附加属性WeakListenerProperty上并且更新目标,同时订阅IMyWeakEventTest.PropertyChanged。

        这时如果我们点击按钮"测试修改[0]",代码执行取_myWeakEventTests索引为0的值并设置WTest为[$"测试修改{_index}"],经过MyWeakEventManagerTest的中转最终执行了MyWeakEventBindingExpTest.ReceiveWeakEvent,目前MyWeakEventBindingExpTest中保存了Source、Target以及DProperty,同时ReceiveWeakEvent将我们修改的属性名给到了,那么顺理成章的将Target的DProperty修改为新的Source的值。

 

 说了这么多那么怎么证明它们之间没有耦合,下面通过内存变化来分析

 

  此时我们截取下内存快照,可以看到各个对象的计数, MyWeakEventTest(模型Source)、MyTextBox(控件Target)、MyWeakEventBindingExpTest(关系及通知管理)都是30个,和我们创建的相符,这个时候我们点击“清除View”即将MyTextBox移除,然后重新截取内存快照

 

 可以发现MyTextBox和 MyWeakEventBindingExpTest已经被回收,可以通过对象引用差异对比计数变化都为-30

 

 这时点击“重置”验证清除Model即将MyWeakEventTest移除,然后重新截取内存快照

 

可以看到MyWeakEventTest已经被回收


————————————————
版权声明:本文为CSDN博主「qhwoaini」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qhwoaini/article/details/125836037

 

标签:INotifyPropertyChanged,刨析,object,private,public,source,WPF,null,void
From: https://www.cnblogs.com/cdaniu/p/16654982.html

相关文章

  • 【WPF】wpf怎么绑定多个值,多个控件 绑定多个CommandParameter 命令参数
    最近有不少wpf新手问wpf的命令怎么绑定多个控件,很多人为此绞尽脑汁,网上的答案找了也没找到靠谱的,其实用MultiBinding就可以了。从.net3.0版本开始,就支持MultiBinding关于......
  • 【WPF】Toolkit.Mvvm 、MvvmLight、Prism8.0比较
    MvvmLight:已经过时,已经被Toolkit.Mvvm取代。前言在Wpf下最常使用的就是Mvvm模式了,有自己造轮子构建Mvvm框架的,也有使用现成的开源项目,我之前一直使用的是轻量级的MvvmLig......
  • 【WPF】六、WPF命令(ICommand)
    使用Command可以实现UI层跟业务层分离,不必在UI层定义事件方法,近而减少耦合。下一章是关于内容变更在界面上重新展示。界面展示_UI层<Windowx:Class="WpfApp1.Window5"......
  • 【WPF】Prism简介
    最近公司让我给其他员工普及一下Prism框架,整理一下资料和思路。于是乎翻译了一下官方的介绍。Prism简介1.Prism能干嘛Prism为程序设计提供指导,旨在帮助用户更加容......
  • WPF的Decorator 、Adorner和AdornerDecorator
    Decorator和Adorner它们都有“装饰品”的意思。Decorator类负责包装某个UI元素,以提供额外的行为。它有一个类型为UIElement的Child属性,其中含有待包装的内容。......
  • WPF应用布局基础
    创建应用WPF应用(.Netframework)C#完成后控制台在下方在grid中存放内容存放的容器有多种<Grid></Grid><StackPanel/><WrapPanel/><DockPanel/><UniformGrid/>不同的存放容......
  • wpf Load
    第一种写法,写在Interaction.Triggers<UserControl> <b:Interaction.Triggers>    <b:EventTriggerEventName="Loaded">      <b:CallMethodAct......
  • WPF背景页面布局
    <Windowx:Class="TimeCalc.View.TestWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsof......
  • 记一次 .NET 某金融企业 WPF 程序卡死分析
    一:背景1.讲故事前段时间遇到了一个难度比较高的dump,经过几个小时的探索,终于给找出来了,在这里做一下整理,希望对大家有所帮助,对自己也是一个总结,好了,老规矩,上WinDBG说......
  • C# 32位程序,申请大内存,附dome(wpf),亲测可用
    1、我是vs2017,在选装vs的时候,需要安装c++模块,因为申请大内存的必要exe存放在vc的某个目录(下面会给出详细的地址)下的2、安装完成在vs的安装目录可找到这个文件,我是社区版本......