◆ ウィンドウにドロップした XAML を表示する
◆ ときどきデザイナと実際の表示が違うので楽にプレビューするために作ったもの
◆ 基本的にはなくていいもの

今週も昔作ったツールの紹介です

ひとことでいうと XAML を表示するものです

基本は VisualStudio のデザイナだけで十分なのですが ときどき実際の動きとデザイナのプレビューが異なることがあって 毎回ビルドして起動して確認するのが大変だったので作ったものです

自動リロードはないですが更新後にウィンドウにドロップするだけで XAML が表示できます
タブ表示なのでタブを切り替えることで 2 つの表示の差を簡単に確認できます
複数のウィンドウで並べて確認したいなら単純にこのソフトを複数起動すればできます

内部的にはドロップされた xaml ファイルを UIElement に変換して Border 要素の子要素として追加します
Window がルートになっているものは使えません
また XamlReader.Load を使っているのでそこでパース可能な xaml のみが対象です

例えばこういう XAML ファイルを開くことが出来ます

<DockPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Label DockPanel.Dock="Top" Height="60" Background="aqua">1</Label>
<Label DockPanel.Dock="Left" Width="60" Background="yellowgreen">2</Label>
<Label DockPanel.Dock="Bottom" Height="60" Background="pink">3</Label>
<Label DockPanel.Dock="Right" Width="60" Background="thistle">4</Label>
<Label Background="orange">5</Label>
</DockPanel>

xamlview-img

ソース

[MainWindow.xaml]
<Window x:Class="XAMLView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:XAMLView"
mc:Ignorable="d"
Title="XAMLView" Height="600" Width="800" FontFamily="Meiryo">
<Grid>
<TabControl x:Name="tabs">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Padding" Value="20 8"/>
</Style>
</TabControl.Resources>
<TabItem Header="NewTab" AllowDrop="True" MouseDown="TabItem_MouseDown">
<Border Drop="Border_Drop" AllowDrop="True" Background="#FFF" />
</TabItem>
<TabItem>
<TabItem.Template>
<ControlTemplate>
<Button Click="Button_Click" Content="+" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="10" Foreground="Gray">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid Margin="6 0">
<Ellipse x:Name="round" Width="16" Height="16" Stroke="Gray"/>
<Line x:Name="crosslineH" X1="5" Y1="8" X2="11" Y2="8" Stroke="Gray" StrokeThickness="2"/>
<Line x:Name="crosslineV" X1="8" Y1="5" X2="8" Y2="11" Stroke="Gray" StrokeThickness="2"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Fill" TargetName="round" Value="#FFF07C8B"></Setter>
<Setter Property="StrokeThickness" TargetName="round" Value="0"></Setter>
<Setter Property="Stroke" TargetName="crosslineH" Value="#f8f8f8"></Setter>
<Setter Property="Stroke" TargetName="crosslineV" Value="#f8f8f8"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</ControlTemplate>
</TabItem.Template>

<Grid Background="#666">
<StackPanel Margin="30" MinWidth="100" MinHeight="100">
<TextBlock Foreground="#FFEACC8C" FontSize="24" FontWeight="Bold" Margin="0 0 0 12">H E L P</TextBlock>
<TextBlock Foreground="#fff0f0" FontSize="16" LineHeight="32">
+ ボタンを押してタブを開いてください。<LineBreak/>
.xaml ファイルをドロップするとプレビューされます。<LineBreak/>
ミドルクリックでタブを閉じます。
</TextBlock>
</StackPanel>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>

[MainWindow.xaml.cs]
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;

namespace XAMLView
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
this.createNewtab();
}

private void TabItem_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Pressed)
{
var tab = sender as TabItem;
this.removeTab(tab);
}
}

private void Border_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length > 0) this.loadFiles(files);
}
}

private TabItem createNewtab()
{
var border = new Border
{
AllowDrop = true,
Background = Brushes.White,
};
border.AddHandler(DropEvent, new DragEventHandler(this.Border_Drop));

var tab = new TabItem
{
Content = border,
Header = "NewTab",
};
tab.AddHandler(MouseDownEvent, new MouseButtonEventHandler(this.TabItem_MouseDown));

var new_index = this.tabs.Items.Count - 1;
this.tabs.Items.Insert(new_index, tab);
this.tabs.SelectedIndex = new_index;

return tab;
}

private void removeTab(TabItem tab)
{
var index = this.tabs.Items.IndexOf(tab);
this.tabs.Items.Remove(tab);
this.tabs.SelectedIndex = Math.Max(0, index - 1);
}

private void loadFiles(string[] files)
{
var is_first = true;
foreach (var filepath in files)
{
if (!filepath.EndsWith(".xaml"))
{
MessageBox.Show("XAML ファイルをドロップしてください。", filepath);
return;
}

try
{
var element = this.readXAML(filepath);
if (element == null)
{
throw new Exception("ルート要素は UIElement である必要があります。");
}

if (is_first)
{
// 1回目
var tab = this.tabs.SelectedItem as TabItem;
this.showXAML(tab, element, filepath);
is_first = false;
}
else
{
// 2回目以降
var tab = this.createNewtab();
this.showXAML(tab, element, filepath);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, filepath);
}
}
}

private void showXAML(TabItem tab, UIElement xaml_root, string filepath)
{
tab.Header = Path.GetFileName(filepath);
var frame = tab.Content as Border;

try
{
frame.Child = xaml_root;
}
catch (Exception ex)
{
frame.Child = new TextBlock { Text = "Error: \n" + ex.Message };
}
}

private UIElement readXAML(string filepath)
{
using (var reader = new StreamReader(filepath))
{
return XamlReader.Load(reader.BaseStream) as UIElement;
}
}
}
}

Gist

XAML 使うなら VisualStudio を使ってるでしょう ということで exe は用意してません
使ってみたいなら各自でビルドお願いします

WPF のプロジェクト新規に作って MainWindow の 2 ファイルを置き換えてビルドすれば使えると思います