ตัวอย่างโปรแกรมอัปโหลดรายการเอกสาร .NET

Jeff Fisher ทีม Google Data APIs
มกราคม 2008

ข้อมูลเบื้องต้น: ขอบเขตของตัวอย่าง

ภาพหน้าจอของอินเทอร์เฟซโปรแกรม

ดาวน์โหลดไฟล์ที่เรียกใช้ได้ตัวอย่าง

ข้อดีอย่างหนึ่งของ Documents List Data API คือช่วยให้นักพัฒนาแอปสร้างเครื่องมือย้ายข้อมูลสำหรับผู้ใช้ที่ยังคงใช้ Google เอกสาร ได้ เพื่อวัตถุประสงค์ในการใช้ API นี้ ฉันได้ใช้ไลบรารีของไคลเอ็นต์ .NET เพื่อสร้างแอปพลิเคชันโปรแกรมอัปโหลด .NET 2.0 ซึ่งตั้งชื่ออย่างเหมาะสมว่า "โปรแกรมอัปโหลด DocList" คุณดูแหล่งที่มาของผู้อัปโหลดได้จาก Subversion

ตัวอย่างนี้มีไว้เพื่อให้ผู้ใช้ย้ายข้อมูลเอกสารจากคอมพิวเตอร์ไปยัง Google เอกสารได้ง่ายๆ ซึ่งช่วยให้ผู้ใช้เข้าสู่ระบบบัญชี Google แล้วลากและวางไฟล์ที่รองรับเพื่ออัปโหลดโดยอัตโนมัติได้ นอกจากนี้ ตัวอย่างยังมีตัวเลือกในการเพิ่มตัวเลือกเมนูคลิกขวาไปยังเชลล์ Windows Explorer เพื่ออัปโหลดไฟล์ด้วย ตัวอย่างนี้มีให้ภายใต้สัญญาอนุญาต Apache 2.0 คุณจึงนำไปใช้เป็นจุดเริ่มต้นสำหรับโปรแกรมของคุณเองได้อย่างอิสระ

บทความนี้มีจุดประสงค์เพื่อแสดงให้เห็นว่าพฤติกรรมบางอย่างของตัวอย่างนั้นทำได้อย่างไรโดยใช้ .NET Framework โดยส่วนใหญ่ประกอบด้วยข้อมูลโค้ดที่มีคำอธิบายประกอบจากส่วนที่เกี่ยวข้อง บทความนี้ไม่ได้กล่าวถึงวิธีก่อสร้างแบบฟอร์มและคอมโพเนนต์ UI อื่นๆ ของแอปพลิเคชัน เนื่องจากมีบทความ Visual Studio มากมายที่อธิบายรายละเอียดในเรื่องนี้ หากคุณสงสัยว่ามีการกำหนดค่าคอมโพเนนต์ UI อย่างไร คุณสามารถโหลดไฟล์โปรเจ็กต์ด้วยตนเองได้โดยดาวน์โหลดไลบรารีไคลเอ็นต์และดูในไดเรกทอรีย่อย "clients\cs\samples\DocListUploader"

การสร้างแอปถาดระบบ

ตัวอย่างแอปในถาด

โดยปกติแล้ว เครื่องมือการย้ายข้อมูลจะสามารถทำงานในระบบปฏิบัติการได้อย่างราบรื่น ซึ่งจะขยายสิ่งที่ระบบปฏิบัติการทำได้โดยไม่รบกวนผู้ใช้มากนัก วิธีหนึ่งในการจัดโครงสร้างเครื่องมือดังกล่าวใน Windows คือการให้เครื่องมือทำงานนอกถาดระบบแทนที่จะทำให้แถบงานรก ซึ่งจะช่วยให้ผู้ใช้มีแนวโน้มที่จะเปิดโปรแกรมทำงานอย่างต่อเนื่องมากกว่าที่จะเปิดโปรแกรมเฉพาะเมื่อต้องการทำงานที่เฉพาะเจาะจง แนวคิดนี้มีประโยชน์อย่างยิ่งสำหรับตัวอย่างนี้ เนื่องจากไม่จำเป็นต้องจัดเก็บข้อมูลเข้าสู่ระบบการตรวจสอบสิทธิ์ลงในดิสก์

แอปพลิเคชันถาดระบบคือแอปพลิเคชันที่ทำงานโดยมีเพียง NotifyIcon ในถาดระบบ (บริเวณใกล้นาฬิกาบนแถบงาน) เป็นหลัก เมื่อออกแบบแอปพลิเคชันดังกล่าว โปรดทราบว่าคุณไม่ต้องการให้รูปแบบหลักของโปรเจ็กต์เป็นรูปแบบที่ผู้ใช้โต้ตอบด้วย แต่ให้สร้างแบบฟอร์มแยกต่างหากเพื่อแสดงเมื่อเรียกใช้แอปพลิเคชัน เราจะอธิบายเหตุผลในเรื่องนี้ให้ชัดเจนในอีกสักครู่

ในตัวอย่างของฉัน ฉันได้สร้างแบบฟอร์ม 2 แบบ ได้แก่ HiddenForm ซึ่งเป็นแบบฟอร์มหลักของแอปพลิเคชันที่มีตรรกะส่วนใหญ่ และ OptionsForm ซึ่งเป็นแบบฟอร์มที่ให้ผู้ใช้ปรับแต่งตัวเลือกบางอย่างและลงชื่อเข้าใช้บัญชี Google นอกจากนี้ ฉันยังเพิ่ม NotifyIcon ที่ชื่อ DocListNotifyIcon ลงใน HiddenForm และปรับแต่งด้วยไอคอนของตัวเอง เพื่อไม่ให้ผู้ใช้เห็น HiddenForm ฉันจึงตั้งค่าความทึบเป็น 0% ตั้งค่า WindowState เป็น Minimized และตั้งค่าพร็อพเพอร์ตี้ ShowInTaskbar เป็น False

โดยปกติแล้ว เมื่อโปรแกรมทำงานนอกถาดระบบ การปิดแอปพลิเคชันไม่ควรหยุดโปรแกรม แต่ควรซ่อนแบบฟอร์มที่ใช้งานอยู่และแสดงเฉพาะ NotifyIcon หากต้องการทำเช่นนี้ เราต้องลบล้างเหตุการณ์ "FormClosing" ของแบบฟอร์มดังนี้

private void OptionsForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if(e.CloseReason == CloseReason.UserClosing) {
        this.Hide();
        e.Cancel = true;
    }
}

นอกจากนี้ เราอาจต้องการซ่อนแบบฟอร์มเมื่อผู้ใช้ย่อหน้าต่าง เนื่องจากไม่มีเหตุผลที่จะใช้พื้นที่บนแถบงานเนื่องจากเรามีไอคอนการแจ้งเตือนอยู่แล้ว ซึ่งทำได้โดยใช้โค้ดต่อไปนี้

private void OptionsForm_Resize(object sender, EventArgs e)
{
    if (this.WindowState == FormWindowState.Minimized)
    {
        this.Hide();
    }
}

ตอนนี้เนื่องจากเราไม่อนุญาตให้ผู้ใช้ปิด OptionsForm เราจึงเก็บอินสแตนซ์เดียวที่เชื่อมโยงกับ HiddenForm ไว้ได้ เมื่อต้องการแสดง OptionsForm อีกครั้ง เราก็เพียงแค่เรียกใช้เมธอด Show ของฟอร์ม

เนื่องจากผู้ใช้มองไม่เห็นรูปแบบหลักของแอปพลิเคชันนี้ ซึ่งก็คือ HiddenForm เราจึงต้องให้วิธีแก่ผู้ใช้ในการออกจากแอปพลิเคชันของเราจริงๆ ฉันเลือกที่จะเพิ่ม ContextMenu ไปยัง NotifyIcon ด้วย ToolStripMenuItem เพื่อปิดแอปพลิเคชัน การเขียนตัวแฮนเดิลการคลิกนั้นง่าย เพียงเรียกใช้เมธอด Close ของ HiddenForm

เคล็ดลับเกี่ยวกับบอลลูน

แอปพลิเคชันถาดระบบจำนวนมากจะสื่อสารกับผู้ใช้โดยแสดงเคล็ดลับในบอลลูน ซึ่งมีลักษณะคล้ายฟองกลมๆ ที่มาจาก NotifyIcon โดยบับเบิลจะแสดงดังนี้

DocListNotifyIcon.ShowBalloonTip(10000, "Title", "Example Text", ToolTipIcon.Info);

อาร์กิวเมนต์แรกคือระยะเวลาเป็นมิลลิวินาทีที่จะแสดงบับเบิล โปรดทราบว่าระบบปฏิบัติการจะอนุญาตให้ใช้ฟิลด์นี้เป็นระยะเวลาขั้นต่ำและสูงสุด ซึ่งก็คือ 10 และ 30 วินาทีตามลำดับ อาร์กิวเมนต์ที่ 2 และ 3 ระบุชื่อและเนื้อหาบางอย่างสำหรับบับเบิล อาร์กิวเมนต์สุดท้ายช่วยให้คุณเลือกไอคอนเพื่อแสดงวัตถุประสงค์ของบับเบิลได้

การอัปโหลดเอกสาร

การอัปโหลดเอกสารนั้นทำได้ง่ายๆ การดำเนินการส่วนใหญ่จะทำโดยใช้วิธีการ UploadDocument ของออบเจ็กต์ DocumentsService กระบวนการนี้อธิบายไว้ชัดเจนยิ่งขึ้นในคู่มือสำหรับนักพัฒนาซอฟต์แวร์ Documents List API

service = new DocumentsService("DocListUploader");
((GDataRequestFactory) service.RequestFactory).KeepAlive = false;
service.setUserCredentials(username, password);

ก่อนอื่นต้องเริ่มต้นออบเจ็กต์ DocumentsService และต้องระบุข้อมูลเข้าสู่ระบบของผู้ใช้ เราได้ปิดใช้ส่วนหัว HTTP "keep-alive" เพื่อป้องกันปัญหาบางอย่างเมื่ออัปโหลดหลายไฟล์ เนื่องจากทราบว่าส่วนหัวนี้ทำให้เกิดปัญหาบางอย่างกับ .NET Framework

lastUploadEntry = service.UploadDocument(fileName, null);

ข้อมูลโค้ดนี้จะอัปโหลดไฟล์ในเส้นทางที่มีอยู่ในสตริง fileName อาร์กิวเมนต์ที่ 2 เป็น Null แสดงว่าชื่อไฟล์ Google เอกสารจะเหมือนกับชื่อไฟล์เดิม

การจัดการการลากและวาง

เพื่อให้การอัปโหลดง่ายขึ้น จึงควรอนุญาตให้ผู้ใช้ลากและวางไฟล์จากโฟลเดอร์ลงในแอปพลิเคชันเพื่ออัปโหลด ขั้นตอนแรกคือการอนุญาตการดำเนินการวางจากไฟล์ โดยโค้ดด้านล่างจะเปลี่ยนเคอร์เซอร์เพื่อระบุว่าอนุญาตให้วางได้

private void OptionsForm_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
    {
        e.Effect = DragDropEffects.Copy;
    }
}

เมื่อวางไฟล์หรือกลุ่มไฟล์แล้ว เราต้องจัดการเหตุการณ์ดังกล่าวโดยไปที่แต่ละไฟล์ที่วางและอัปโหลดไฟล์

private void OptionsForm_DragDrop(object sender, DragEventArgs e)
{
    string[ fileList = (string[) e.Data.GetData(DataFormats.FileDrop);

    foreach (string file in fileList)
    {
      mainForm.UploadFile(file);
    }
}

เอกสารที่แสดง

การรับรายการเอกสารจากเซิร์ฟเวอร์เป็นวิธีที่ดีในการเตือนผู้ใช้ว่าได้อัปโหลดอะไรไปแล้วบ้าง ข้อมูลโค้ดด้านล่างใช้DocumentsServiceออบเจ็กต์ที่เราเริ่มต้นก่อนหน้านี้เพื่อดึงเอกสารทั้งหมดจากเซิร์ฟเวอร์

public DocumentsFeed GetDocs()
{
    DocumentsListQuery query = new DocumentsListQuery();
    DocumentsFeed feed = service.Query(query);
    return feed;
}

วิธีที่สะดวกในการแสดงข้อมูลนี้คือการใช้ ListView ฉันเพิ่ม ListView ชื่อ DocList ลงใน OptionsForm แล้ว นอกจากนี้ ผมยังสร้าง ImageList ที่กำหนดเองของไอคอนเพื่อแสดงเอกสารประเภทต่างๆ ด้วย โค้ดต่อไปนี้จะแสดงข้อมูลที่ดึงมาจากฟีดด้านบนใน ListView

public void UpdateDocList()
{

    DocList.Items.Clear();


    DocumentsFeed feed = mainForm.GetDocs();


    foreach (DocumentEntry entry in feed.Entries)
    {
        string imageKey = "";
        if (entry.IsDocument)
        {
            imageKey = "Document.gif";
            
        }
        else if (entry.IsSpreadsheet)
        {
            imageKey = "Spreadsheet.gif";
        }
        else
        {
            imageKey = "Presentation.gif";
        }

        ListViewItem item = new ListViewItem(entry.Title.Text, imageKey);
        item.SubItems.Add(entry.Updated.ToString());
        item.Tag = entry;
        DocList.Items.Add(item); 
    }

    foreach (ColumnHeader column in DocList.Columns)
    {
        column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
    }
}

imageKey ตัวแปรจะเลือกรูปภาพใน ImageList ที่เชื่อมโยงซึ่งควรใช้สำหรับแต่ละแถว พร็อพเพอร์ตี้ Tag ใช้ที่นี่เพื่อจัดเก็บรายการเดิม ซึ่งอาจมีประโยชน์ในการดำเนินการกับเอกสารในภายหลัง สุดท้ายนี้ เราจะใช้AutoResize เพื่อจัดรูปแบบความกว้างของคอลัมน์ใน ListView โดยอัตโนมัติ

การเปิดเอกสารในเบราว์เซอร์

เนื่องจากเอกสารเหล่านี้จัดเก็บไว้ใน Google เอกสาร จึงควรอนุญาตให้ผู้ใช้ดูเอกสารในเบราว์เซอร์ของตนเอง Windows มีฟังก์ชันในตัวที่ใช้ทำสิ่งนี้ได้ ดังนี้

using System.Diagnostics;

private void OpenSelectedDocument()
{
    if (DocList.SelectedItems.Count > 0)
    {
        DocumentEntry entry = (DocumentEntry) DocList.SelectedItems[0].Tag;
        Process.Start(entry.AlternateUri.ToString());
    }
}

ในที่นี้ เราจะดึงข้อมูลรายการเดิมออกจากพร็อพเพอร์ตี้ Tag จากนั้นใช้ AlternateUri ของเอกสารที่เลือกเพื่อเรียก Process.Start ส่วนที่เหลือจะได้รับการจัดการโดยความมหัศจรรย์ของ .NET Framework

การเพิ่มเมนูตามบริบทของ Shell

วิธีที่ง่ายที่สุดในการเพิ่มรายการลงในเมนูตามบริบทของ Shell คือการแก้ไขรีจิสทรี สิ่งที่เราต้องทำคือสร้างรายการใน HKEY_CLASSES_ROOT ที่ชี้ไปยังแอปพลิเคชันของเรา โปรดทราบว่าการดำเนินการนี้จะเปิดอินสแตนซ์ใหม่ของแอปพลิเคชันเมื่อผู้ใช้คลิกรายการเมนู ซึ่งเป็นสิ่งที่เราจะต้องจัดการในส่วนต่อไปนี้

using Microsoft.Win32;

public void Register()
{
    RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME+"\\command");

    if (key == null)
    {
        key = Registry.ClassesRoot.CreateSubKey("*\\shell\\" + KEY_NAME + "\\command");
    }

    key.SetValue("", Application.ExecutablePath + " \"%1\"");
}

โค้ดนี้จะสร้างคีย์รีจิสทรีไปยังตำแหน่งที่แอปพลิเคชันที่กำลังทำงานอยู่ ใช้สัญกรณ์ "%1" เพื่อระบุว่าควรส่งไฟล์ที่เลือกในเชลล์ภายในพารามิเตอร์นี้ KEY_NAME คือค่าคงที่สตริงที่กำหนดซึ่งกำหนดข้อความของรายการในเมนูตามบริบท

public void UnRegister()
{
    RegistryKey key = Registry.ClassesRoot.OpenSubKey("*\\shell\\"+KEY_NAME);

    if (key != null)
    {
        Registry.ClassesRoot.DeleteSubKeyTree("*\\shell\\"+KEY_NAME);
    }
}

วิธีนี้จะนำคีย์ที่กำหนดเองที่เราเพิ่มออกหากมี

การป้องกันอินสแตนซ์หลายรายการ

เนื่องจากแอปพลิเคชันของเราอยู่ในถาดระบบ เราจึงไม่ต้องการให้โปรแกรมหลายอินสแตนซ์ทํางานพร้อมกัน เราสามารถใช้ Mutex เพื่อให้มั่นใจว่าจะมีอินสแตนซ์เดียวที่ทำงานอยู่

using System.Threading;

bool firstInstance;
Mutex mutex = new Mutex(true, "Local\\DocListUploader", out firstInstance);
if (!firstInstance)
{
  return;
}

คุณวางโค้ดด้านบนไว้ในเมธอด Main ของแอปพลิเคชันเพื่อออกก่อนเวลาได้หากโปรแกรมของเรากำลังทำงานอยู่ เนื่องจาก Mutex อยู่ในเนมสเปซ "Local" จึงทำให้เซสชันอื่นในเครื่องสามารถเรียกใช้แอปพลิเคชันของเราแยกกันได้ อย่างไรก็ตาม คุณควรระมัดระวังเป็นพิเศษเนื่องจากเรากำลังแก้ไขรีจิสทรีส่วนกลาง

การสื่อสารระหว่างโปรเซส

เมื่อผู้ใช้คลิกรายการเมนูบริบทของ Shell สำหรับไฟล์ที่เราเพิ่มไว้ก่อนหน้านี้ ระบบจะเปิดอินสแตนซ์ใหม่ของแอปพลิเคชันของเราและระบุเส้นทางแบบเต็มไปยังตำแหน่งที่ไฟล์อยู่บนดิสก์ ตอนนี้ต้องสื่อสารข้อมูลนี้ไปยังอินสแตนซ์ของแอปพลิเคชันที่กําลังทํางานอยู่ ซึ่งทำได้โดยใช้กลไก IPC ของ .NET Framework ที่เปิดตัวในเวอร์ชัน 2.0

using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;

ข้อความที่เราส่งจะมีรูปแบบเป็นออบเจ็กต์ที่กำหนดเอง ในที่นี้ ฉันได้สร้างออบเจ็กต์ที่มีการอ้างอิงกลับไปยัง HiddenForm ซึ่งมีตรรกะของแอปพลิเคชันนี้ เนื่องจากออบเจ็กต์นี้จะโฮสต์ในอินสแตนซ์เดิม จึงเป็นวิธีที่อินสแตนซ์ในภายหลังใช้สื่อสารกับรูปแบบหลักของอินสแตนซ์เดิม

class RemoteMessage : MarshalByRefObject
{
    private HiddenForm mainForm;

    public RemoteMessage(HiddenForm mainForm)
    {
        this.mainForm = mainForm;
    }

    public void SendMessage(string file)
    {
        mainForm.HandleUpload(file);
    }
}

เมื่อเริ่มต้นอินสแตนซ์แรกของแอปพลิเคชัน โค้ดต่อไปนี้จะช่วยให้แอปพลิเคชันฟังอินสแตนซ์ที่ต่อเนื่องได้

public void ListenForSuccessor()
{
    IpcServerChannel serverChannel = new IpcServerChannel("DocListUploader");
    ChannelServices.RegisterChannel(serverChannel, false);

    RemoteMessage remoteMessage = new RemoteMessage(this);
    RemotingServices.Marshal(remoteMessage,"FirstInstance");
    
}

โปรดสังเกตในส่วนด้านบนว่ามีการลงทะเบียนช่อง IPC ที่มีชื่อ และจัดทำสำเนาของออบเจ็กต์ RemoteMessage ที่เรากำหนด โดยเริ่มต้นด้วยการอ้างอิงถึงตัวมันเอง

สำหรับอินสแตนซ์ที่ต่อเนื่องของโปรแกรม สตริงที่ระบุให้กับ Main ผ่านพารามิเตอร์ args จะต้องส่งต่อไปยังอินสแตนซ์เดิม คุณเรียกใช้โค้ดต่อไปนี้เพื่อเชื่อมต่อกับช่อง IPC ที่กำลังฟังและดึงออบเจ็กต์ RemoteMessage จากอินสแตนซ์เดิมได้ จากนั้นจะใช้SendMessageเพื่อส่งชื่อไฟล์ไปยังอินสแตนซ์เดิม

public static void NotifyPredecessor(string file)
{
    IpcClientChannel clientChannel = new IpcClientChannel();
    ChannelServices.RegisterChannel(clientChannel, false);

    RemoteMessage message = (RemoteMessage) Activator.GetObject(typeof(RemoteMessage), 
      "ipc://DocListUploader/FirstInstance");

    if (!message.Equals(null))
    {
        message.SendMessage(file);
    }
}

ระบบการรับส่งข้อความจากระยะไกลมีประสิทธิภาพมาก เนื่องจากเราสามารถทำให้ออบเจ็กต์ที่เป็นของอินสแตนซ์หนึ่งของโปรแกรมมองเห็นได้ผ่านช่อง IPC ในเครื่องไปยังอินสแตนซ์อื่นๆ

บทสรุป

บทความนี้จะอธิบายวิธีการและเคล็ดลับต่างๆ ที่ใช้ในตัวอย่างโปรแกรมอัปโหลด DocList เพื่อจัดเตรียมยูทิลิตีการย้ายข้อมูลที่ใช้งานง่ายสำหรับ Google เอกสาร คุณยังคงเพิ่มฟังก์ชันการทำงานอีกมากมายในแอปพลิเคชันของคุณเองได้ และคุณสามารถขยายตัวอย่างให้เหมาะกับวัตถุประสงค์ของคุณเองได้

ต่อไปนี้คือแหล่งข้อมูลที่เป็นประโยชน์สำหรับนักพัฒนาแอปที่สนใจใช้ Documents List Data API รวมถึงผู้ที่ต้องการใช้ .NET กับ Google Data API อื่นๆ