۱۳۸۹/۰۹/۲۷

WPF4 و ويندوز 7 : به خاطر سپاري ليست آخرين فايل‌هاي گشوده شده توسط برنامه


اگر به برنامه‌هاي جديد نوشته شده براي ويندوز 7 دقت كنيم، از يك سري امكانات مخصوص آن جهت بهبود دسترسي پذيري به قابليت‌هايي كه ارائه مي‌دهند، استفاده شده است. براي مثال برنامه‌ي OneNote مجموعه‌ي آفيس را در نظر بگيريد. اگر بر روي آيكون آن در نوار وظيفه‌ي ويندوز كليك راست كنيم، ليست آخرين فايل‌هاي گشوده شده توسط آن مشخص است و با كليك بر روي هر كدام، به سادگي مي‌توان اين فايل را گشود. يك چنين قابليتي در منوي آغازين ويندوز نيز تعبيه شده است (شكل‌هاي زير):




خبر خوب اينكه براي اضافه كردن اين قابليت به برنامه‌هاي WPF4 نيازي به كد نويسي نيست و اين موارد كه تحت عنوان استفاده از Jump list ويندوز 7 تعريف شده‌اند، با كمي دستكاري فايل App.Xaml برنامه، فعال مي‌گردند:

<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True" />
</JumpList.JumpList>
</Application>
همين! از اين پس هر فايلي كه توسط برنامه‌ي شما با استفاده از common file dialog boxes باز شود به صورت خودكار به ليست مذكور اضافه مي‌گردد (بديهي است Jump lists جزو ويژگي‌هاي ويندوز 7 است و در ساير سيستم عامل‌ها نديد گرفته خواهد شد).


سؤال: من اينكار را انجام دادم ولي كار نمي‌كنه!؟

پاسخ: بله. كار نمي‌كنه! اين قابليت تنها زماني فعال خواهد شد كه علاوه بر نكته‌ي فوق، پسوند فايل يا فايل‌هايي نيز به برنامه‌ي شما منتسب شده باشد. اين انتساب‌ها مطلب جديدي نيست و در تمام برنامه‌هاي ويندوزي بايد توسط بكارگيري API ويندوز مديريت شود. قطعه كد زير اينكار را انجام خواهد داد:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Common.Files
{
//from : http://www.devx.com/vb2themax/Tip/19554?type=kbArticle&trk=MSCP
public class FileAssociation
{
const int ShcneAssocchanged = 0x8000000;
const int ShcnfIdlist = 0;

public static void CreateFileAssociation(
string extension,
string className,
string description,
string exeProgram)
{
// ensure that there is a leading dot
if (extension.Substring(0, 1) != ".")
extension = string.Format(".{0}", extension);

try
{
if (IsAssociated(extension)) return;

// create a value for this key that contains the classname
using (var key1 = Registry.ClassesRoot.CreateSubKey(extension))
{
if (key1 != null)
{
key1.SetValue("", className);
// create a new key for the Class name
using (var key2 = Registry.ClassesRoot.CreateSubKey(className))
{
if (key2 != null)
{
key2.SetValue("", description);
// associate the program to open the files with this extension
using (var key3 = Registry.ClassesRoot.CreateSubKey(string.Format(@"{0}\Shell\Open\Command", className)))
{
if (key3 != null) key3.SetValue("", string.Format(@"{0} ""%1""", exeProgram));
}
}
}
}
}

// notify Windows that file associations have changed
SHChangeNotify(ShcneAssocchanged, ShcnfIdlist, 0, 0);
}
catch (Exception ex)
{
//todo: log ...
}
}

// Return true if extension already associated in registry
public static bool IsAssociated(string extension)
{
return (Registry.ClassesRoot.OpenSubKey(extension, false) != null);
}

[DllImport("shell32.dll")]
public static extern void SHChangeNotify(int wEventId, int uFlags, int dwItem1, int dwItem2);
}
}
و مثالي از نحوه‌ي استفاده از آن:
private static void createFileAssociation()
{
var appPath = Assembly.GetExecutingAssembly().Location;
FileAssociation.CreateFileAssociation(".xyz", "xyz", "xyz File",
appPath
);
}
لازم به ذكر است كه اين كد در ويندوز 7 فقط با دسترسي مديريتي قابل اجرا است (كليك راست و اجرا به عنوان ادمين) و در ساير حالات با خطاي Access is denied متوقف خواهد شد. به همين جهت بهتر است برنامه‌ي نصاب مورد استفاده اين نوع انتسابات را مديريت كند؛ زيرا اكثر آن‌ها با دسترسي مديريتي است كه مجوز نصب را به كاربر جاري خواهند داد. اگر از فناوري Click once استفاده مي‌كنيد به اين مقاله و اگر براي مثال از NSIS كمك مي‌گيريد به اين مطلب مراجعه نمائيد.


سؤال: من اين كارها را هم انجام دادم. الان به چه صورت از آن‌ استفاده كنم؟

زمانيكه كاربري بر روي يكي از اين فايل‌هاي ذكر شده در ليست آخرين فايل‌هاي گشوده شده توسط برنامه كليك كند، آدرس اين فايل به صورت يك آرگومان به برنامه ارسال خواهد شد. براي مديريت آن در WPF بايد به فايل App.Xaml.cs مراجعه كرده و چند سطر زير را به آن افزود:
    public partial class App
{
public App()
{
this.Startup += appStartup;
}

void appStartup(object sender, StartupEventArgs e)
{
if (e.Args.Any())
{
this.Properties["StartupFileName"] = e.Args[0];
}
}
//...

در اين كد، e.Args حاوي مسير فايل انتخابي است. براي مثال در اينجا مقدار آن به خاصيت StartupFileName انتساب داده شده است. اين خاصيت در برنامه‌هاي WPF به صورت يك خاصيت عمومي تعريف شده است و در سراسر برنامه (مثلا در رخداد آغاز فرم اصلي آن يا هر جاي ديگري) به صورت زير قابل دسترسي است:
var startupFileName = Application.Current.Properties["StartupFileName"];

سؤال: برنامه‌ي من از OpenFileDialog براي گشودن فايل‌ها استفاده نمي‌كند. آيا راه ديگري براي افزودن مسيرهاي باز شده به Jump lists ويندوز 7 وجود دارد؟

پاسخ: بله. همانطور كه مي‌دانيد عناصر XAML با اشياء دات نت تناظر يك به يك دارند. به اين معنا كه JumpList تعريف شده در ابتداي اين مطلب در فايل App.XAML ، دقيقا معادل كلاسي به همين نام در دات نت فريم ورك است (تعريف شده در فضاي نام System.Windows.Shell) و با كد نويسي نيز قابل دسترسي و مديريت است. براي مثال:
var jumpList = JumpList.GetJumpList(App.Current);
var jumpPath = new JumpPath();
jumpPath.Path = "some path goes here....";
// If the CustomCategory property is null
// or Empty, the item is added to the Tasks category
jumpPath.CustomCategory = "Files";
JumpList.AddToRecentCategory(jumpPath);
jumpList.Apply();
به همين ترتيب،‌ JumpPath ذكر شده در كدهاي فوق، در كدهاي XAML نيز قابل تعريف است:
<Application x:Class="Win7Wpf4.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True">
<JumpPath
CustomCategory="Files"
Path="Some path goes here..."
/>
</JumpList>
</JumpList.JumpList>
</Application>