天天看點

2019-8-28-WPF-開發

title author date CreateTime categories
WPF 開發 lindexi 2019-8-28 11:3:39 +0800 2018-2-13 17:23:3 +0800 WPF

本文:我遇到的WPF的坑

标記方法被使用

使用 UsedImplicitly 特性可以标記一個沒有被引用的方法為反射使用,這時就不會被優化删除。

public class Foo
{
    [UsedImplicitly]
    public Foo()
    {
        //反射調用
    }

    public Foo(string str)
    {
        //被引用
    }
}      

拼接 URI 路徑

我需要将一個 URI 和另一個 URI 拼接如 ​

​https://blog.lindexi.com/post/123​

​​ 和 ​

​/api/12​

​​ 拼接,拿到絕對路徑 ​

​https://blog.lindexi.com/api/12​

​ 可以使用下面方法

var uri1 = new Uri("https://blog.lindexi.com/post/123");
var uri2 = "/api/12";

    if (Uri.TryCreate(uri1, uri2, out var absoluteUrl))
    {
        // 拼接成功,在這裡就可以使用 absoluteUrl 拼接後的絕對路徑
    }      

單例應用在多執行個體使用者無法使用

如果使用NamedPipeServerStream、​

​Mutex​

​做單執行個體,需要傳入字元串,這時如果傳入一個固定的字元串,會在多使用者的時候無法使用。

因為如果在一個使用者啟動的軟體,那麼就注冊了這個字元串,在另一個使用者就無法啟動。解決方法是傳入​

​Environment.UserName​

​。

在構造函數傳入​

​Environment.UserName​

​有關的字元串就可以在一個使用者進行單例,其他使用者打開是自己的軟體。

public partial class App
{
    #region Constants and Fields

    /// <summary>The event mutex name.</summary>
    private const string UniqueEventName = "{GUID}";

    /// <summary>The unique mutex name.</summary>
    private const string UniqueMutexName = "{GUID}"; //這裡需要加 Environment.UserName

    /// <summary>The event wait handle.</summary>
    private EventWaitHandle eventWaitHandle;

    /// <summary>The mutex.</summary>
    private Mutex mutex;

    #endregion

    #region Methods

    /// <summary>The app on startup.</summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The e.</param>
    private void AppOnStartup(object sender, StartupEventArgs e)
    {
        bool isOwned;
        this.mutex = new Mutex(true, UniqueMutexName, out isOwned);
        this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);

        // So, R# would not give a warning that this variable is not used.
        GC.KeepAlive(this.mutex);

        if (isOwned)
        {
            // Spawn a thread which will be waiting for our event
            var thread = new Thread(
                () =>
                {
                    while (this.eventWaitHandle.WaitOne())
                    {
                        Current.Dispatcher.BeginInvoke(
                            (Action)(() => ((MainWindow)Current.MainWindow).BringToForeground()));
                    }
                });

            // It is important mark it as background otherwise it will prevent app from exiting.
            thread.IsBackground = true;

            thread.Start();
            return;
        }

        // Notify other instance so it could bring itself to foreground.
        this.eventWaitHandle.Set();

        // Terminate this instance.
        this.Shutdown();
    }

    #endregion
}      

當滑鼠滑過一個被禁用的元素時,讓ToolTip 顯示

設定​

​ToolTipService.ShowOnDisabled​

​為 true

<Button ToolTipService.ShowOnDisabled="True">      

擷取裝置螢幕數量

通過 WinForms 方法擷取

System.Windows.Forms.Screen.AllScreens      

上面就可以拿到所有的螢幕,通過 Count 方法就可以知道有多少螢幕

var screenCount = Screen.AllScreens.Length;      

擷取目前域使用者

在 WPF 找到目前登陸的使用者使用下面代碼

using System.Security.Principal;

// 其他代碼

            WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent();
            string crentUserAd = windowsIdentity.Name;      

輸出 ​

​crentUserAd​

​​ 可以看到 ​

​裝置\\使用者​

​ 的格式

綁定資源檔案裡面的資源

在 WPF 的 xaml 可以通過 ​

​x:Static​

​ 綁定資源,但是要求資源檔案裡面的對應資源設定通路為公開

如果沒有設定那麼将會在 xaml 運作的時候提示

System.Windows.Markup.XamlParseException 

在 System.Windows.Markup.StaticExtension 上提供值xxx      

此時在設計器裡面是可以看到綁定成功,隻是在運作的時候提示找不到,展開可以看到下面提示

無法将 xx.Properties.Resources.xx  StaticExtension 值解析為枚舉、靜态字段或靜态屬性      

解決方法是在 Resource.resx 裡面的通路權限從 internal 修改為 public 就可以

判斷 WPF 程式使用管理者權限運作

引用命名空間,複制下面代碼,然後調用 IsAdministrator 方法,如果傳回 true 就是使用管理者權限運作

using System.Security.Principal;

        public static bool IsAdministrator()
        {
            WindowsIdentity current = WindowsIdentity.GetCurrent();
            WindowsPrincipal windowsPrincipal = new WindowsPrincipal(current);
            //WindowsBuiltInRole可以枚舉出很多權限,例如系統使用者、User、Guest等等
            return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
        }      

 ​

注冊全局事件

如果需要注冊一個類型的全局事件,如拿到 TextBox 的全局輸入,那麼可以使用下面代碼

EventManager.RegisterClassHandler(typeof(TextBox), TextBox.KeyDownEvent, new RoutedEventHandler(方法));      

高版本的 WPF 引用低版本類庫導緻無法啟動

如果在一個 .net 4.0 的 WPF 程式引用一個 .net 2.0 的庫,那麼就會讓程式無法運作,解決方法添加​

​useLegacyV2RuntimeActivationPolicy​

打開 app.config 添加 ​

​useLegacyV2RuntimeActivationPolicy="true"​

​ 在 startup 元素

下面是 app.config 代碼

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>      

 ​

非托管使用托管委托

如果有一個 C++ 寫的dll,他需要一個函數指針,在C#使用,就可以傳入委托。

那麼簡單的方法是這樣寫:

private static void Func(){}
    public void C()
    {
        c(Func);
    }      

其中c就是C++寫的函數,傳進去看起來好像正常。

但是有時候程式不知道怎麼就炸了。

因為這樣寫是不對的。

傳入的不是函數位址,傳入的是把函數隐式轉換委托,然後轉換的委托是局部變量,會被gc,是以在C++拿到的是一個被回收的委托,調用時就會炸。

這裡無法用catch,是以用這個會讓程式退出。

調用C#的函數,使用委托,是隐式轉換,上面代碼可以寫成下面的

private static void Func(){}
    public void C()
    {
         var temp = new delegate(){ Func };
         c(temp);
    }      

于是在函數完就把temp放到gc在調用時找不到委托。

一個好的做法

private static void Func(){}
    private delegate Temp { get; } = new delegate(){Func};
    private void C()
    {
        c(Temp);
    }      

放在靜态變量不會gc調用不會空,可以這樣不會出現上面問題。

元素失去獲得

元素可以使用 CaptureMouse 方法獲得,這可以用在拖動,一旦拖動出元素可以獲得,得到拖動結束。

但是有時會失去獲得,如果自己需要失去,可以使用 Mouse.Capture(null) 但是在沒有自己使用的這個函數,失去獲得,可以的是:

設定元素可命中false,如果看到元素失去互動,而且堆棧沒有任何地方使用失去獲得,那麼可能就是存在設定元素可命中false。

如果有兩個函數同時 獲得 一個元素,會不會出現 失去獲得?不會,如果同一個元素多次 獲得,那麼不會出現失去獲得。如果這是讓另一個獲得,那麼這個元素就是失去獲得。可以通過元素.IsMouseCaptured 判斷元素獲得。

可以通過 Mouse.Captured 獲得現在 Mouse 是否獲得。如果傳回是 null ,沒有獲得,但是元素獲得存在一些問題,在失去焦點或其他,可能就失去獲得。

​​CaptureMouse/CaptureStylus 可能會失敗 - walterlv​​

反射引用程式集

這是比較難以說明的問題,總之,可能出現的問題就是引用了一個 xaml 使用的資源庫,或使用了一個隻有反射才通路的庫。

原因: 如果在引用一個庫,引用代碼沒有直接使用的程式集。使用的方法就是使用 xaml 或反射來使用。那麼在生成,vs 不會把程式集放在輸出檔案夾。

問題: 反射報錯,無法找到程式集。

例子: 如果我用了一個程式集,然而代碼沒有直接引用,而是反射使用,這樣,vs判斷這個程式集沒有使用,最後把他清除。是以會出現反射無法拿到,而且很難知道這裡出現坑。

為了解決 xaml 和反射無法拿到的坑,可以使用 在任意位置使用 Debug.Write(typeof(程式集裡的一個類)) 方法讓 vs 引用程式集。

那麼在 Release 上為何還可以把程式集放在輸出檔案夾呢?因為我也不知道原因,如果你知道的話,那麼請告訴我一下。

使用十進制設定顔色

在 xaml 如果需要使用 十進制設定顔色,請使用下面代碼

<SolidColorBrush x:Key="LikeGreen">
        <SolidColorBrush.Color>
            <Color R="100" G="200" B="30" A="100"/>
        </SolidColorBrush.Color>
    </SolidColorBrush>      

​​https://stackoverflow.com/a/47952098/6116637​​

WPF 判斷檔案是否隐藏

可以設定一些檔案是隐藏檔案,那麼 WPF 如何判斷 FileInfo 是隐藏檔案?

簡單的代碼,通過判斷 Attributes 就可以得到,請看下面。

file.Attributes.HasFlag(FileAttributes.Hidden)      

觸發滑鼠事件

觸發滑鼠點下事件,可以使用下面代碼

element.RaiseEvent(new MouseEventArgs(Mouse.PrimaryDevice, 1)
            {
                RoutedEvent = Mouse.MouseDownEvent
            });      

TextBlock 換行

使用 ​

​&#10;​

​ 就可以換行

​​win10 uwp 在 xaml 讓 TextBlock 換行​​

在 xaml 綁定索引空格

如果一個索引需要傳入空格,那麼在 xaml 使用下面代碼是無法綁定

{Binding MyCollection[foo bar]}      

需要使用下面代碼

{Binding MyCollection[[foo&x20;bar]]}      

​​Binding to an index with space in XAML – Ivan Krivyakov​​

使用 Task ContinueWith 在主線程

在有時候使用 Task 的 Delay 之後想要傳回主線程,可以使用 ContinueWith 的方法,請看代碼

Task.Delay(TimeSpan.FromSeconds(5)).ContinueWith
            (
                _ => Foo()
                // 如果 Foo 不需要在主線程,請注釋下面一段代碼
                , TaskScheduler.FromCurrentSynchronizationContext()
            );      

核心是 TaskScheduler.FromCurrentSynchronizationContext 方法

如果 Foo 不需要在主線程,就可以删除 TaskScheduler.FromCurrentSynchronizationContext 代碼

WPF-資料綁定:日期時間格式

{Binding datetime,StringFormat='{}{0:yyyy年MM月dd日 dddd HH:mm:ss}',ConverterCulture=zh-CN}      

指定ConverterCulture為zh-CN後星期就顯示為中文了。

WPF 第三方DLL 強簽名

WPF 去掉最大化按鈕

通過在視窗添加下面代碼

ResizeMode="NoResize"      

視窗就剩下一個關閉同時使用者也無法拖動修改視窗大小

WPF TextBox 全選

在一個按鈕點選的時候全選 TextBox 的内容,可以在按鈕裡面調用 SelectAll 方法

textBox.SelectAll();      

上面代碼的 textBox 就是界面寫的 TextBox 元素

如果發現調用上面的代碼 TextBox 沒有全選,可能是 TextBox 沒有拿到焦點,可以嘗試下面代碼

textBox.Focus();
textBox.SelectAll();      

WPF 擷取文本光标寬度

通過 ​

​SystemParameters.CaretWidth​

​ 擷取寬度

var caretWidth = SystemParameters.CaretWidth;