MS/WPF-Samples/Clipboard源码分析


最近打算自己开发一款Windows平台的软件,但查看相关文档之后仍对一些功能不够熟练,偶然间发现Github上有微软官方发布的实例代码,故打算研究研究这些代码弄懂WPF平台上开发的一些细节问题。

我决定随缘研究这些代码,毕竟有这么多,就先挑自己觉得有用的、用得上的示例来研究好了。

附上项目下载地址,大家可以下载后参照着来看:WPF-Samples

ClipboardViewer

在Clipboard目录下共有两个示例项目,先分析这个剪切板查看器,运行后的界面如下:

MS/WPF-Samples/Clipboard源码分析

可以看到界面中大体上就是显示及设置剪切板内容和格式的功能,Xaml代码看了看有些冗长,而且基本上就是普通的布局,甚至连数据绑定都没有,因此直接对c#代码进行分析。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    // This is called when UI is laid out, rendered and ready for interaction.
    private void WindowLoaded(object sender, RoutedEventArgs args)
    {
        // Check the current Clipboard data format status
        RefreshClipboardDataFormat();

        // Set the text copy information on RichTextBox
        SetTextOnRichTextBox("Please type clipboard copy data here!");
    }

	// Update the data format status and describe all available data formats
    private void RefreshClipboardDataFormat()
    {
        clipboardInfo.Clear();

        lbPasteDataFormat.Items.Clear();

        var dataObject = GetDataObjectFromClipboard();

        if (dataObject == null)
        {
            clipboardInfo.Text = "Can't access clipboard now! Please click Refresh button again.";
        }
        else
        {
            // Check the data format whether it is on Clipboard or not
            cbAudio.IsChecked = dataObject.GetDataPresent(DataFormats.WaveAudio);
            cbFileDropList.IsChecked = dataObject.GetDataPresent(DataFormats.FileDrop);
            cbImage.IsChecked = dataObject.GetDataPresent(DataFormats.Bitmap);
            cbText.IsChecked = dataObject.GetDataPresent(DataFormats.Text);
            cbRtf.IsChecked = dataObject.GetDataPresent(DataFormats.Rtf);
            cbXaml.IsChecked = dataObject.GetDataPresent(DataFormats.Xaml);

            // Update the data format into the information panel
            UpdateAvailableDataFormats(dataObject);
        }
    }

	// Set the plain text on RichTextBox
    private void SetTextOnRichTextBox(string text)
    {
        var document = richTextBox.Document;
        var textRange = new TextRange(document.ContentStart, document.ContentEnd) {Text = text};
        richTextBox.Focus();
        richTextBox.SelectAll();
    }
}

以上代码中,构造函数没什么说的。但是WindowLoaded函数则让我有些疑惑,我明明没有在构造函数中添加Loaded事件,而且在Window类中也并无与Load相关的事件。于是我在这个方法内部加了断点发现果然在窗口加载完毕也就是弹出窗口但内容却一片空白的情况下执行到了断点处,那么这应该就涉及到窗口的生命周期的问题了。

MS/WPF-Samples/Clipboard源码分析

看了一下官方文档,又回头看了看Window类的方法,这个Loaded方法到底去哪儿了呢?

MS/WPF-Samples/Clipboard源码分析

终于,查阅API文档,发现它是继承了FrameworkElement,在该类的Loaded方法描述中我发现了同样的一句话:

Occurs when the element is laid out, rendered, and ready for interaction.

MS/WPF-Samples/Clipboard源码分析

那么就是它了,但还有一个问题,不同名的函数为什么可以调用呢?

首先我注意到了RoutedEventArgs这个类型的参数,我想是不是参数不同导致系统可以直接识别这个方法就是执行到Loaded之后用户想要运行的方法呢?但我观察其他事件接收函数也同样是这个类型的参数,那系统还怎么识别呢?但我还抱有侥幸心理,于是。。。

MS/WPF-Samples/Clipboard源码分析

我只是改了一下名字而已,三个错误。。那也就排除了系统可以识别参数的选项,同时似乎也说明了这是系统内置的方法名,但我又一次翻看系统API,还是没有找到该方法名。

再次阅读该错误,说是MainWindow没有WindowLoaded的定义,那就是说有这个声明了。果然,在Xaml中发现了这个声明。。。哎,自作聪明。。

MS/WPF-Samples/Clipboard源码分析

好了,虽然绕了一大圈,但终究解决了疑问并且学到了知识不是么,下面正式进入主题。可以看到在Loaded方法中首先检查了剪切板中数据的格式,然后在富文本编辑框中输入提示信息,下面先分析RefreshClipboardDataFormat方法。

1、clipboardInfo是界面中右上方的显示剪切板内容的一大块文本框的name,清空它的内容。

2、lbPasteDataFormat是Paste(Get) Format:下面显示剪切板所含对象的列表,清空所有项目。

3、GetDataObjectFromClipboard方法从剪切版中获取所含数据的对象

// Get DataObject from the system clipboard
private IDataObject GetDataObjectFromClipboard()
{
    IDataObject dataObject;

    try
    {
        dataObject = Clipboard.GetDataObject();
    }
    catch (COMException)
    {
        // Clipboard.GetDataObject can be failed by opening the system clipboard 
        // from other or processing clipboard operation like as setting data on clipboard
        dataObject = null;
    }

    return dataObject;
}

该方法从剪切板获取数据并返回。注意到该方法的返回值为IDataObject,该接口为Windows下面的,那应该是系统为我们定义的接口。

MS/WPF-Samples/Clipboard源码分析

MS/WPF-Samples/Clipboard源码分析

4、根据从剪切板获取的数据设置界面中左上角多选框的状态,其中DataFormates类提供了一组预定义数据格式名

字段 字段值 描述
Bitmap “Bitmap” 指定 Microsoft Windows 位图数据格式。
CommaSeparatedValue “CSV” 指定以逗号分隔的值 (CSV) 数据格式。
Dib “DeviceIndependentBitmap” 指定与设备无关位图 (DIB) 数据格式。
Dif “DataInterchangeFormat” 指定 Windows 数据交换格式 (DIF) 数据格式。
EnhancedMetafile “EnhancedMetafile” 指定 Windows 增强型图元文件格式。
FileDrop “FileDrop” 指定 Windows 文件放置格式。
Html “HTML Format” 指定 HTML 数据格式。
Locale “区域设置” 指定 Windows 区域设置(区域性)数据格式。
MetafilePicture “MetaFilePict” 指定 Windows 图元文件图片数据格式。
OemText “OEMText” 指定标准 Windows OEM 文本数据格式。
Palette “Palette” 指定 Windows 调色板数据格式。
PenData “PenData” 指定 Windows 钢笔数据格式。
Riff “RiffAudio” 指定 资源交换文件格式 (RIFF) 音频数据格式。
Rtf “丰富文本格式” 指定 RTF 格式 (RTF) 数据格式。
Serializable “PersistentObject” 指定封装任何类型的可序列化数据对象的数据格式。
StringFormat “System.String” 指定 公共语言运行时 (CLR) 字符串类数据格式。
SymbolicLink “SymbolicLink” 指定 Windows 符号链接数据格式。
Text “Text” 指定 ANSI 文本数据格式。
Tiff “TaggedImageFileFormat” 指定 标记图像文件格式 (TIFF) 数据格式。
UnicodeText “UnicodeText” 指定 Unicode 文本数据格式。
WaveAudio “WaveAudio” 指定波形音频数据格式。
Xaml “Xaml” 指定 可扩展应用程序标记语言 (XAML) 数据格式。
XamlPackage “XamlPackage” 指定 可扩展应用程序标记语言 (XAML) 包数据格式。

5、更新格式信息到信息面板当中,此时调用了UpdateAvailableDataFormats方法,方法很简单,没有需要说明的地方。

// Update and describe all available data formats
private void UpdateAvailableDataFormats(IDataObject dataObject)
{
    clipboardInfo.AppendText("Clipboard DataObject Type: ");
    clipboardInfo.AppendText(dataObject.GetType().ToString());

    clipboardInfo.AppendText("\n\n****************************************************\n\n");

    var formats = dataObject.GetFormats();

    clipboardInfo.AppendText(
        "The following data formats are present in the data object obtained from the clipboard:\n");

    if (formats.Length > 0)
    {
        foreach (var format in formats)
        {
            bool nativeData;
            if (dataObject.GetDataPresent(format, false))
            {
                nativeData = true;
                clipboardInfo.AppendText("\t- " + format + " (native)\n");
            }
            else
            {
                nativeData = false;
                clipboardInfo.AppendText("\t- " + format + " (autoconvertable)\n");
            }

            if (nativeData)
            {
                lbPasteDataFormat.Items.Add(format);
            }
            else if ((bool) cbAutoConvertibleData.IsChecked)
            {
                lbPasteDataFormat.Items.Add(format);
            }
        }

        lbPasteDataFormat.SelectedIndex = 0;
    }
    else
    {
        clipboardInfo.AppendText("\t- no data formats are present\n");
    }
}

Loaded的第一步分析完了,第二步就是把提示文字(纯文本)设置到富文本框并全选方便用户直接粘贴。

// Set the plain text on RichTextBox
private void SetTextOnRichTextBox(string text)
{
    var document = richTextBox.Document;
    var textRange = new TextRange(document.ContentStart, document.ContentEnd) {Text = text};
    richTextBox.Focus();
    richTextBox.SelectAll();
}

小结

以上操作中与剪切板有关的操作只有Clipboard.GetDataObject,该方法返回的IDataObject对象我们需要有一定的了解。

剩下的代码我针对较为重要的进行分析。

Clipboard.Clear();	// 清空剪切板

private object GetDataFromDataObject(IDataObject dataObject, string dataFormat, bool autoConvert)
{
    object data = null;

    try
    {
        data = dataObject.GetData(dataFormat, autoConvert);
     }
    catch (COMException)
    {
        // Fail to get the data by the invalid value like tymed(DV_E_TYMED) 
        // or others(Aspect, Formatetc).
        // It depends on application's IDataObject::GetData implementation.
        clipboardInfo.AppendText("Fail to get data!!! ***COMException***");
    }
    catch (OutOfMemoryException)
    {
        // Fail by the out of memory from getting data on Clipboard. 
        // Occurs with the low memory.
        clipboardInfo.AppendText("Fail to get data!!! ***OutOfMemoryException***");
    }

    return data;
}

上面的方法是从IDataObject接口的对象中获取真正的对象,方法为dataObject.GetData(dataFormat, autoConvert);,可能会因为值不合法或者内存过低而引发异常,因此需要对其捕获异常。

clipboardInfo.AppendText(data?.ToString() ?? "null");

上面的代码是某个方法里面的一句,这种写法引起了我的关注。

可空类型修饰符(?):引用类型可以使用空引用表示一个不存在的值,而值类型通常不能表示为空。

例如:string str=null; 是正确的,int i=null; 编译器就会报错。
为了使值类型也可为空,就可以使用可空类型,即用可空类型修饰符"?“来表示,表现形式为"T?”
例如:int? 表示可空的整形,DateTime? 表示可为空的时间。

T? 其实是System.Nullable(泛型结构)的缩写形式,也就意味着当你用到T?时编译器编译 时会把T?编译成System.Nullable的形式。
例如:int?,编译后便是System.Nullable的形式。

空合并运算符(??):
用于定义可空类型和引用类型的默认值。如果此运算符的左操作数不为null,则此运算符将返回左操作数,否则返回右操作数。

例如:a??b 当a为null时则返回b,a不为null时则返回a本身。
空合并运算符为右结合运算符,即操作时从右向左进行组合的。如,“a??b??c”的形式按“a??(b??c)”计算。

// Get text data string from RichTextBox content which encoded as UTF8
private string GetTextStringFromRichTextBox(string dataFormat)
{
    var document = richTextBox.Document;
    var textRange = new TextRange(document.ContentStart, document.ContentEnd);

    if (dataFormat == DataFormats.Text)
    {
         return textRange.Text;
    }
    Stream contentStream = new MemoryStream();
    textRange.Save(contentStream, dataFormat);

    if (contentStream.Length > 0)
    {
        var bytes = new byte[contentStream.Length];

        contentStream.Position = 0;
        contentStream.Read(bytes, 0, bytes.Length);

        var utf8Encoding = Encoding.UTF8;

        return utf8Encoding.GetString(bytes);
    }

    return null;
}

以上代码的功能是从将富文本格式的内容转换为纯文本内容,逻辑上分为两个部分,如果需要转换的内容已经是纯文本,就直接返回,否则进行转换后返回。

第一部分需要记住的点是从富文本框中获取纯文本使用的TextRange.Text属性。第二部分就是真正的转换纯文本的操作了,先用指定格式将富文本框中的内容转换为流,然后以UTF-8的格式读取字符并返回。

// Set the selected data format's data from RichTextBox's content 
// into DataObject for copying data on the system clipboard
private void CopyDataFromRichTextBox(IDataObject dataObject)
{
    if ((bool) cbCopyTextDataFormat.IsChecked)
    {
        var textData = GetTextStringFromRichTextBox(DataFormats.Text);
        if (textData != string.Empty)
        {
            dataObject.SetData(DataFormats.Text, textData);
        }
    }

    if ((bool) cbCopyXamlDataFormat.IsChecked)
    {
        var textData = GetTextStringFromRichTextBox(DataFormats.Xaml);
        if (textData != string.Empty)
        {
            dataObject.SetData(DataFormats.Xaml, textData);
        }
    }

    if ((bool) cbCopyRtfDataFormat.IsChecked)
    {
        var textData = GetTextStringFromRichTextBox(DataFormats.Rtf);
        if (textData != string.Empty)
        {
            dataObject.SetData(DataFormats.Rtf, textData);
        }
    }

    // Finally, consider a custom, application defined format.
    // We use an arbitrary encoding here, for demonstration purposes.
    if ((bool) cbCustomSampleDataFormat.IsChecked)
    {
        Stream customStream = new MemoryStream();

        var textData = "This is Custom Sample Data Start\n\n" +
                       GetTextStringFromRichTextBox(DataFormats.Text) +
                       "\nCustom Sample Data End.";

        var bytesUnicode = Encoding.Unicode.GetBytes(textData);
        var bytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, bytesUnicode);

        if (bytes.Length > 0)
        {
            customStream.Write(bytes, 0, bytes.Length);
            dataObject.SetData("CustomSample", customStream);
        }
    }
}

// Set the selected data format's data from the file content 
// into DataObject for copying data on the system clipboard
private void CopyDataFromFile(IDataObject dataObject)
{
    string fileName = null;

    var dialog = new OpenFileDialog {CheckFileExists = true};

    if ((bool) cbCopyTextDataFormat.IsChecked)
    {
        dialog.Filter = "Plain Text (*.txt)|*.txt";
        dialog.ShowDialog();
        fileName = dialog.FileName;

        if (string.IsNullOrEmpty(fileName))
        {
            return;
        }

        var fileEncoding = Encoding.Default;
        var textData = GetTextStringFromFile(fileName, fileEncoding);
        if (!string.IsNullOrEmpty(textData))
        {
            dataObject.SetData(DataFormats.Text, textData);
        }
    }

    if ((bool) cbCopyRtfDataFormat.IsChecked)
    {
        fileName = null;
        dialog.Filter = "RTF Documents (*.rtf)|*.rtf";
        dialog.ShowDialog();
        fileName = dialog.FileName;

        if (string.IsNullOrEmpty(fileName))
        {
            return;
        }

        var fileEncoding = Encoding.ASCII;
        var textData = GetTextStringFromFile(fileName, fileEncoding);
        if (!string.IsNullOrEmpty(textData))
        {
            dataObject.SetData(DataFormats.Rtf, textData);
        }
    }

    if ((bool) cbCopyXamlDataFormat.IsChecked)
    {
        fileName = null;
        dialog.Filter = "XAML Flow Documents (*.xaml)|*.xaml";
        dialog.ShowDialog();
        fileName = dialog.FileName;

        if (string.IsNullOrEmpty(fileName))
        {
            return;
        }

        var fileEncoding = Encoding.UTF8;
        var textData = GetTextStringFromFile(fileName, fileEncoding);
        if (!string.IsNullOrEmpty(textData))
        {
            dataObject.SetData(DataFormats.Xaml, textData);
        }
    }

    // Finally, consider a custom, application defined format.
    // We use an arbitrary encoding here, for demonstartion purposes.
    if ((bool) cbCustomSampleDataFormat.IsChecked)
    {
        fileName = null;
        dialog.Filter = "All Files (*.*)|*.*";
        dialog.ShowDialog();
        fileName = dialog.FileName;

        if (string.IsNullOrEmpty(fileName))
        {
            return;
        }

        var fileEncoding = Encoding.UTF8;
        var textData = GetTextStringFromFile(fileName, fileEncoding);
        if (string.IsNullOrEmpty(textData)) return;
        var bytesUnicode = Encoding.Unicode.GetBytes(textData);
        var bytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, bytesUnicode);

        if (bytes.Length > 0)
        {
            var customStream = new MemoryStream();
            customStream.Write(bytes, 0, bytes.Length);
            dataObject.SetData("CustomSample", customStream);
        }
    }
}

private void CopyToClipboard(object sender, RoutedEventArgs args)
{
    var dataObject = new DataObject();

    // Copy data from RichTextBox/File content into DataObject
    if ((bool) rbCopyDataFromRichTextBox.IsChecked)
    {
        CopyDataFromRichTextBox(dataObject);
    }
    else
    {
        CopyDataFromFile(dataObject);
    }

    // Copy DataObject on the system Clipboard
    if (dataObject.GetFormats().Length > 0)
    {
        if ((bool) cbFlushOnCopy.IsChecked)
        {
            // Copy data to the system clipboard with flush
            Clipboard.SetDataObject(dataObject, true /*copy*/);
        }
        else
        {
            // Copy data to the system clipboard without flush
            Clipboard.SetDataObject(dataObject, false /*copy*/);
        }
    }

            // Dump the copied data contents on the information panel
    DumpAllClipboardContentsInternal();
}

前面两个方法分别从富文本框和文件去读取数据,从而将数据赋值到IDataObject对象中的。最后一个方法将得到的数据设置到剪切板上,其中还展示了如何自定义格式并粘贴到剪切板的方法。Clipboard.SetDataObject方法将指定的数据对象置于系统剪贴板中,并接受一个布尔参数,该参数指示应用程序退出时是否将数据对象保留在剪贴板中。

// Paste a selected paste data format's data to RichTextBox
private void PasteClipboardDataToRichTextBox(string dataFormat, IDataObject dataObject, bool autoConvert)
{
    if (dataObject != null && dataObject.GetFormats().Length > 0)
    {
        var pasted = false;

        if (dataFormat == DataFormats.Xaml)
        {
            var xamlData = dataObject.GetData(DataFormats.Xaml) as string;
            if (!string.IsNullOrEmpty(xamlData))
            {
			pasted = PasteTextDataToRichTextBox(DataFormats.Xaml, xamlData);
            }
        }
        else if (dataFormat == DataFormats.Rtf)
        {
            var rtfData = dataObject.GetData(DataFormats.Rtf) as string;
            if (!string.IsNullOrEmpty(rtfData))
            {
                pasted = PasteTextDataToRichTextBox(DataFormats.Rtf, rtfData);
            }
        }
        else if (dataFormat == DataFormats.UnicodeText
                 || dataFormat == DataFormats.Text
                 || dataFormat == DataFormats.StringFormat)
        {
            var textData = dataObject.GetData(dataFormat) as string;
            if (textData != string.Empty)
            {
                SetTextOnRichTextBox(textData);
                pasted = true;
            }
        }
        else if (dataFormat == "CustomSample")
        {
            // Paste the application defined custom data format's data to RichTextBox content
            var customStream = dataObject.GetData(dataFormat, autoConvert) as Stream;
            if (customStream.Length > 0)
            {
                var textRange = new TextRange(richTextBox.Document.ContentStart,
                    richTextBox.Document.ContentEnd);
                textRange.Load(customStream, DataFormats.Text);
                pasted = true;
            }
        }

        if (!pasted)
        {
            MessageBox.Show(
                "Can't paste the selected data format into RichTextBox!\n\nPlease click Refresh button to update the current clipboard format Or select File RadioButton to paste data.",
                "Paste Data Format Error",
                MessageBoxButton.OK,
                MessageBoxImage.Exclamation);
        }
    }
}

// Paste a selected paste data format's data to the fileSSS
private void PasteClipboardDataToFile(string dataFormat, IDataObject dataObject, bool autoConvert)
{
    var dialog = new SaveFileDialog {CheckFileExists = false};

    if (dataFormat == DataFormats.Text
        || dataFormat == DataFormats.UnicodeText
        || dataFormat == DataFormats.StringFormat)
    {
        dialog.Filter = "Plain Text (*.txt)|*.txt | All Files (*.*)|*.*";
    }
    else if (dataFormat == DataFormats.Xaml)
    {
        dialog.Filter = "XAML Flow Documents (*.xaml)|*.xaml | All Files (*.*)|*.*";
    }
    else if (dataFormat == DataFormats.Rtf)
    {
        dialog.Filter = "RTF Documents (*.rtf)|*.rtf | All Files (*.*)|*.*";
    }
    else
    {
        dialog.Filter = "All Files (*.*)|*.*";
    }

    if (!(bool) dialog.ShowDialog())
    {
        return;
    }

    var fileName = dialog.FileName;

    if (dataFormat == DataFormats.Xaml)
    {
        var xamlData = dataObject.GetData(DataFormats.Xaml) as string;
        if (!string.IsNullOrEmpty(xamlData))
        {
            PasteTextDataToFile(dataFormat, xamlData, fileName, Encoding.UTF8);
        }
    }
    else if (dataFormat == DataFormats.Rtf)
    {
        var rtfData = dataObject.GetData(DataFormats.Rtf) as string;
        if (!string.IsNullOrEmpty(rtfData))
        {
            PasteTextDataToFile(dataFormat, rtfData, fileName, Encoding.ASCII);
        }
    }
            else if (dataFormat == DataFormats.UnicodeText
             || dataFormat == DataFormats.Text
             || dataFormat == DataFormats.StringFormat)
    {
        var textData = dataObject.GetData(dataFormat, autoConvert) as string;
        if (!string.IsNullOrEmpty(textData))
        {
            PasteTextDataToFile(dataFormat, textData, fileName,
                dataFormat == DataFormats.Text ? Encoding.Default : Encoding.Unicode);
        }
    }
    else
    {
        // Paste the CustomSample data or others to the file
        //Stream customStream = dataObject.GetData(dataFormat, autoConvert) as Stream;
        var customStream = GetDataFromDataObject(dataObject, dataFormat, autoConvert) as Stream;
        if (customStream != null && customStream.Length > 0)
        {
            PasteStreamDataToFile(customStream, fileName);
        }
    }
}
		// Paste text data on RichTextBox as UTF8 encoding
private bool PasteTextDataToRichTextBox(string dataFormat, string textData)
{
    var pasted = false;

    var document = richTextBox.Document;
    var textRange = new TextRange(document.ContentStart, document.ContentEnd);

    Stream stream = new MemoryStream();

    var bytesUnicode = Encoding.Unicode.GetBytes(textData);
    var bytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, bytesUnicode);

    if (bytes.Length > 0 && textRange.CanLoad(dataFormat))
    {
        stream.Write(bytes, 0, bytes.Length);
        textRange.Load(stream, dataFormat);
        pasted = true;
    }

    return pasted;
}

// Paste text data to the file with the file encoding
private void PasteTextDataToFile(string dataFormat, string textData, string fileName, Encoding fileEncoding)
{
    FileStream fileWriteStream;

    try
    {
        fileWriteStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write);
    }
    catch (IOException)
    {
        MessageBox.Show("File is not acessible.\n", "File Write Error");
        return;
    }

    fileWriteStream.SetLength(0);

    var bytesUnicode = Encoding.Unicode.GetBytes(textData);
    var bytes = Encoding.Convert(Encoding.Unicode, fileEncoding, bytesUnicode);

    if (bytes.Length > 0)
    {
        fileWriteStream.Write(bytes, 0, bytes.Length);
    }

    fileWriteStream.Close();
}

// Paste stream data to the file
private void PasteStreamDataToFile(Stream stream, string fileName)
{
    FileStream fileWriteStream = null;

    try
    {
        fileWriteStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write);
    }
    catch (IOException)
    {
        MessageBox.Show("File is not acessible.\n", "File Write Error");
    }

    fileWriteStream.SetLength(0);

    var bytes = new byte[stream.Length];

    stream.Position = 0;
    stream.Read(bytes, 0, bytes.Length);

    if (bytes.Length > 0)
    {
        fileWriteStream.Write(bytes, 0, bytes.Length);
    }

    fileWriteStream.Close();
}

上面是将剪切板内容粘贴到富文本框或文件中的方法。

ClipboardSpy

因为不懂Spy是什么意思,所以对这个比较害怕,以为是比较难的东西,所以就留到最后了,没想到打开一看,就是Viewer的简化版,看了下代码也没什么新知识,那就只好草草结束这一小节了。

MS/WPF-Samples/Clipboard源码分析

总结

剪切板相关

系统中与剪切板直接相关的操作比较简单,只有三个:

ClipBoard.SetDateObject();  // 设置剪切板数据,第二个参数确定是否在程序退出后清空该数据
Clipboard.GetDataObject();  // 获取剪切板数据
Clipboard.Clear();  // 清空剪切板

但无论是设置内容到剪切板还是真正把内容取回本地都需要对数据进行格式判断以获取到正确的信息,而在IDataObject接口预定义的那么多类型中我们常用的大概也就是文本、图片和文件这几种类型了,注意区分即可。

dataObject.GetData(DataFormats.Xaml);  // 获取指定类型数据

其他

  • 所有会发生异常的地方微软都为用一个方法专门来做,可能这样更容易定位异常吧。
  • 控件中的Name以小写字母开头,代码中的方法名都是大写字母。
  • Mainwindow的开头用了partial来修饰,这代表这个类并不是用这一个类就能完成的,这也就间接说明了还需要同名Xaml文件的原因,毕竟Xaml文件最后也肯定要转换为类的。

创意时间

我觉得只是知道要怎么用是不够的,实践才是最好的老师。下面我将根据这次学到的这点知识,思考怎样做出自己的创意出来。

有一种场景,我们需要不停地在几个内容之间重复复制粘贴,那么我们的操作步骤就应该是选中内容、Ctrl+C、切换、Ctrl+V,然后不断重复此步骤。这其中最耗时间的应该是选中内容这一步骤,这一点我觉得做客服的应该深有体会。

所以我打算做这样一款软件,预定义用户设置好的内容列表,最多二十条记录,用快捷键的方式响应选中内容、Ctrl+C甚至切换这几个步骤。

当然我现在的知识不够,就先完成一个简易版本,实现:点击列表复制项目。

MS/WPF-Samples/Clipboard源码分析

用了差不多一个小时的时间做了一下,在数据绑定这一块还是不太熟悉,而且也只用了纯文本格式去做,比较简单,后期等技术熟练了再实践这个完整的想法。

项目比较小,就直接把代码贴在这里了。

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="1*"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="1*"/>
            </Grid.RowDefinitions>
            <ListView Name="copyList" ItemsSource="{Binding}" SelectionChanged="Select_Changed"/>
            <WrapPanel Grid.Row="1">
                <Button Name="addBtn" Content="添加" Click="AddBtn_Click"/>
                <Button Name="clearBtn" Content="清空" Click="ClearBtn_Click"/>
            </WrapPanel>
            <TextBox Name="editor" Grid.Row="2"/>
        </Grid>
        <GridSplitter Grid.Column="1" Width="5"/>
        <ScrollViewer Grid.Column="2">
            <TextBlock Name="showMsg"/>
        </ScrollViewer>
    </Grid>
private ObservableCollection<object> _copies = new ObservableCollection<object>();

        public MainWindow()
        {
            InitializeComponent();
            copyList.DataContext = _copies;
        }

        // 点击添加按钮添加文本到列表当中
        private void AddBtn_Click(object sender, RoutedEventArgs e)
        {
            if (editor.Text.Length > 0)
            {
                _copies.Add(editor.Text);
                editor.Clear();
            }
        }

        private void ClearBtn_Click(object sender,RoutedEventArgs e)
        {
            _copies.Clear();
            showMsg.Text = "";
        }

        private void Select_Changed(object sender,RoutedEventArgs e)
        {
            Clipboard.SetText((string)copyList.SelectedItem);
            showMsg.Text += Clipboard.GetText() + "====已复制到剪切板\n";
        }