Việc xây dựng chuỗi URI tài nguyên WPF theo chuẩn pack://application:,,,/AssemblyName;component/Path/To/Resource thường tốn thời gian và dễ nhầm lẫn do sự khác biệt giữa đường dẫn vật lý, cấu trúc dự án và quy ước định dạng URI. Một tiện ích tích hợp trực tiếp vào Visual Studio giúp tự động sinh URL từ tập tin được chọn trong Solution Explorer là giải pháp thực tiễn nhằm tăng năng suất phát triển WPF.
Phân tích yêu cầu cốt lõi
- Lấy đối tượng
ProjectItemđang được chọn trong Solution Explorer. - Xác định thư mục gốc của dự án chứa tập tin đó (không phải thư mục giải pháp).
- Tính toán đường dẫn tương đối từ thư mục gốc dự án đến tập tin đã chọn.
- Chuẩn hóa đường dẫn: thay dấu
\bằng/, loại bỏ ký tự đầu tiên nếu là/. - Ghép thành URI WPF hợp lệ với tên assembly và phần
component.
Mã nguồn plugin dạng lệnh độc lập (console)
Dưới đây là phiên bản được tái cấu trúc hoàn toàn — sử dụng System.Text.RegularExpressions để xử lý đường dẫn an toàn hơn, loại bỏ logic phụ thuộc vào Marshal.GetActiveObject (có thể gây lỗi trên một số phiên bản VS), đồng thời áp dụng kiểu khai báo rõ ràng và kiểm tra null nghiêm ngặt:
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using EnvDTE;
using EnvDTE80;
namespace WpfUriGenerator
{
internal static class Program
{
[STAThread]
private static void Main()
{
try
{
var dte = Package.GetGlobalService(typeof(DTE)) as DTE2;
if (dte == null) throw new InvalidOperationException("Không thể truy cập DTE.");
var solutionExplorer = dte.ToolWindows.SolutionExplorer;
var selected = solutionExplorer.SelectedItems as IEnumerable;
if (selected == null || !selected.Cast<object>().Any()) return;
var items = selected.Cast<object>()
.Select(x => x as UIHierarchyItem)
.Where(x => x?.Object is ProjectItem)
.Select(x => x.Object as ProjectItem)
.ToArray();
var targetItem = items.FirstOrDefault();
if (targetItem == null) return;
var project = targetItem.ContainingProject;
var projectDir = Path.GetDirectoryName(project.FullName);
var fullPath = targetItem.FileNames[0];
if (!fullPath.StartsWith(projectDir, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("Tập tin không nằm trong thư mục dự án.");
var relativePath = fullPath.Substring(projectDir.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var normalizedPath = Regex.Replace(relativePath, @"[\\/]+", "/");
var assemblyName = Path.GetFileNameWithoutExtension(project.FullName);
var wpfUri = $"pack://application:,,,/{assemblyName};component/{normalizedPath}";
Console.WriteLine(wpfUri);
System.Windows.Forms.Clipboard.SetText(wpfUri);
}
catch (Exception ex)
{
Console.WriteLine($"Lỗi: {ex.Message}");
}
Console.ReadKey();
}
}
}
Phiên bản tích hợp Visual Commander (ICommand)
Đây là lớp lệnh có thể đăng ký trực tiếp trong Visual Commander, hỗ trợ chạy từ menu ngữ cảnh trong Solution Explorer:
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Shell;
using VisualCommanderExt;
public class GenerateWpfResourceUri : ICommand
{
public void Run(DTE2 dte, Package package)
{
try
{
var solutionExplorer = dte.ToolWindows.SolutionExplorer;
var selected = solutionExplorer.SelectedItems as IEnumerable;
if (selected == null) return;
var item = selected.Cast<object>()
.Select(x => x as UIHierarchyItem)
.FirstOrDefault(x => x?.Object is ProjectItem)?.Object as ProjectItem;
if (item == null) return;
var project = item.ContainingProject;
var projectDir = Path.GetDirectoryName(project.FullName);
var fullPath = item.FileNames[0];
var relativePath = fullPath.Substring(projectDir.Length)
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var normalizedPath = Regex.Replace(relativePath, @"[\\/]+", "/");
var assemblyName = Path.GetFileNameWithoutExtension(project.FullName);
var uri = $"pack://application:,,,/{assemblyName};component/{normalizedPath}";
System.Windows.Forms.Clipboard.SetText(uri);
}
catch
{
// Bỏ qua lỗi — không hiển thị thông báo để tránh làm gián đoạn UX
}
}
}
Cấu hình lệnh trong Visual Commander
- Mở Tools → Visual Commander → Editor.
- Tạo mới một lệnh kiểu
ICommand, dán mã trên vào. - Lưu và đóng trình soạn thảo.
- Vào Tools → Customize → Commands → Context menu, chọn
SolutionExplorer. - Thêm lệnh vừa tạo vào danh sách, kéo thả vào vị trí mong muốn (ví dụ: sau "Open With…").
Sau khi hoàn tất, chỉ cần nhấn chuột phải vào bất kỳ tập tin nào trong Solution Explorer (như Images/logo.png, Styles/Theme.xaml) và chọn lệnh — URI WPF sẽ được sao chép vào clipboard ngay lập tức.