【WPF】學習筆記(一)——做一個簡單的電子簽名板


參加實習(WPF)已經有兩個多周的時間了,踩了一些坑,也算積累了一些小東西,准備慢慢拿出來分享一下。(●'◡'●)

這次呢就講講一個簡單的電子簽名板的實現。

先上張圖(PS:字寫得比較丑,不要太在意哈):

 

1.任務目標

最基本的需求:1.簽名功能 2.清除簽名 3.保存簽名(讓用戶選擇文件夾、簽名保存為PNG格式的圖片)

嘗試額外功能:1.Ctrl + Z實現撤銷功能 2.Ctrl + Y實現重做功能 3.保存簽名后打開文件位置並選中文件

 

2.搞事情

1)UI方面

如圖,總體來說,一個InkCanvas加上兩個Button就解決問題了。

A. InkCanvas

 

<InkCanvas Grid.Column="1" Grid.Row="1" Background="White" Height="240" Name="ink">
    <InkCanvas.DefaultDrawingAttributes>
        <DrawingAttributes Color="#FF000000" StylusTip="Ellipse" Height="6" Width="6" IgnorePressure="False" FitToCurve="False">
            <!--調整畫筆形狀-->
            <DrawingAttributes.StylusTipTransform>
                <!--https://msdn.microsoft.com/library/system.windows.media.matrix(v=vs.110).aspx-->
                <Matrix M11="1" M12="0" M21="0" M22="1" OffsetX="0" OffsetY="0"/>
            </DrawingAttributes.StylusTipTransform>
        </DrawingAttributes>
    </InkCanvas.DefaultDrawingAttributes>
</InkCanvas>

 

關於調整畫筆形狀的部分(對,就是那個矩陣),就我個人來說並不是很了解,所以就不作什么解釋了,感興趣的童鞋可以訪問對應的微軟官方文檔查看相關資料。

B. Button

<Button x:Name="btnClearSign" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Padding="0" Margin="12,6,0,0" Click="btnClearSign_Click">
    <Button.Template>
        <ControlTemplate>
            <Grid>
                <Label Cursor="Hand" Foreground="Red" FontFamily="Microsoft YaHei UI" FontSize="20">
                    <Underline>
                        <Run Text="清除簽名"></Run>
                    </Underline>
                </Label>
            </Grid>
        </ControlTemplate>
    </Button.Template>
</Button>

圖中的兩個按鈕都是同一個套路,所以就只展示一個按鈕的代碼。(PS:為了讓按鈕顯得不要太俗,我們為按鈕弄一個類似於超鏈接的樣式)

 

2)邏輯代碼

簽名功能我們就不用操心了,InkCanvas會處理好的。

A. 清除簽名

ink.Strokes.Clear();

這么一行代碼就足夠了。說明一下,這里的ink就是我們在UI部分寫的那個InkCanvas。

 

 B.將簽名保存為PNG圖片

// 判斷簽名板內是否有內容
if (ink.Strokes.Any())
{
    // 讓用戶自己選擇文件夾保存
    // 需要在工程中添加對System.Windows.Forms的引用
    // References => Add Reference => 勾選 System.Windows.Forms 項 => OK
    var folderPicker = new FolderBrowserDialog();
    var res = folderPicker.ShowDialog();

    // 判斷用戶有沒有選中文件夾
    if (res == System.Windows.Forms.DialogResult.Cancel) return;

    // 文件保存路徑
    var folderPath = folderPicker.SelectedPath;
    var fileName = DateTime.Now.ToString("yyyyMMddHHmmss");
    var fileUri = folderPath + "\\" + fileName + ".png";

    // windows系統下默認dpi貌似為96,但目前本機測試認為dpi設置為72較為合適
    // dpi的大小會直接影響簽名保存結果是否完整,關於dpi的知識網上還是比較多的,請各位自行了解
    // 下一行代碼的第三個參數用於確定位圖的橫向dpi,第四個參數為縱向dpi
    var renderBitmap = new RenderTargetBitmap((int)ink.ActualWidth, (int)ink.ActualHeight, 72d, 72d, PixelFormats.Pbgra32);
    renderBitmap.Render(ink);

    using (var stream = new FileStream(fileUri, FileMode.Create))
    {
        var encoder = new PngBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
        encoder.Save(stream);
    }

    undoList.Clear();

    // 打開簽名文件所在位置
    FileUtil.LocateFile(fileUri);
}
else
{
    System.Windows.MessageBox.Show("尚未進行簽名,不能執行保存操作!");
}

注:A.這個部分存在一定的問題,請容許我在另一篇的博客中進行相關解釋。

     B.代碼中的undoList.Clear() 以及FileUtil.LocateFile(fileUri) 各位暫時不用理睬,稍后我會進行相關解釋。

     C.下方圖片講解的是如何添加對System.Windows.Forms的引用。

 

C.實現撤銷和重做功能

由於InkCanvas自身實現貌似並沒這樣的方法,所以,我們就自己動動手吧。方法其實還是比較簡單的:首先我們需要明白的是,InkCanvas將每一個筆划都以一個Stroke類的對象保存在一個集合里邊(InkCanvas的Strokes屬性,StrokeCollection類型)。所以,實現撤銷/重做功能就變成了對一個Collection的操作,撤銷即移除頂部的元素(當然我們需要將移除的元素暫存一下,以便后續的重做操作),重做即向Collection頂部增添一項。下面來看看代碼:

Stack<Stroke> undoList = new Stack<Stroke>();

聲明一個全局變量(Stroke的一個棧),用於存儲進行撤銷操作時移除的Stroke,也用於在進行重做功能時提供資源。

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.KeyDown += (s, args) =>
    {
        // Undo => 檢測 Ctrl + Z
        if((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && args.Key == Key.Z)
        {
            if (ink.Strokes.Any())
            {
                undoList.Push(ink.Strokes[ink.Strokes.Count - 1]);
                ink.Strokes.RemoveAt(ink.Strokes.Count - 1);
            }
        }

        // Redo => 檢測 Ctrl + Y
        if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && args.Key == Key.Y)
        {
            if (undoList.Any())
            {
                ink.Strokes.Add(undoList.Pop());
            }
        }
    };
}

在Window的Loaded事件里加上對Ctrl + Z以及Ctrl + Y的檢測,具體套路就如上方代碼中顯示的那樣。

 

D.打開簽名所在位置

先扯點題外話,這個地方我使用的時P/Invoke的方式,調用C++的方法進行實現的。由於我自己對跨語言調用這一塊知之甚少,所以無法做出多少解釋,只是在運氣作用下一番摸索后達到了目的而已。如果以后感覺對這一塊了解更多一些東西后,再單獨寫一篇博客進行相關解釋。

回到正題,先上代碼:

public static class FileUtil
{
    /// <summary>
    /// 依據給定文件路徑,打開文件位置並選中
    /// </summary>
    /// <param name="path">文件完全路徑</param>
    public static void LocateFile(string path)
    {
        /* // 此方法會導致每次新開一個文件資源管理器窗口,不喜歡
         * string domain = "";
         * var psi = new ProcessStartInfo("Explorer.exe");
         * psi.Arguments = "/c,/select," + path;
         * domain = psi.Domain;
         * var p = Process.Start(psi);
         */

        IntPtr ppidl = IntPtr.Zero;
        uint psfgaoOut;
        FileManager.SHParseDisplayName(path, IntPtr.Zero, out ppidl, 0, out psfgaoOut);

        var res = FileManager.OpenFolderAndSelectItems(ppidl, 0, IntPtr.Zero, 0);

    }


    class FileManager
    {
        [DllImport("shell32.dll", EntryPoint = "SHOpenFolderAndSelectItems")]
        public static extern long OpenFolderAndSelectItems(IntPtr pidlFolder, UInt32 cidl, IntPtr apidl, UInt32 dwFlags);

        [DllImport("shell32.dll", EntryPoint = "SHParseDisplayName")]
        public static extern void SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr bindingContext, [Out()] out IntPtr pidl, uint sfgaoIn, [Out()] out uint psfgaoOut);
    }
}

這個家伙又要開始偏(嗶)題(嗶)了,請不用理睬:

正如代碼中所說的,注釋的部分也可以在一定程度上實現我們的需求,但它存在一定的問題。所以我就果斷尋求另一個解決方案,終於打探到shell32.dll(位於Windows\System32目錄下)里的SHOpenFolderAndSelectItems方法可以滿足我的需求。在經歷了一段時間的搜索相關資料,又看了看這位哥的經驗分享后,我終於用C#的方式把SHOpenFolderAndSelectItems方法懟成了上方代碼中的模樣。但是我悲催的發現,只有OpenFolderAndSelectItems方法貌似依舊不行(根本沒有正確的定位到對應的文件/文件夾),在經過一番資料查閱[msdn, pinvoke.net]后,總算是搞出了個可用的版本。

3.Demo

http://files.cnblogs.com/files/lary/UserSignatureDemo.rar

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM