◆ App.LoadComponent を実行すると相対パスのベースがロードした URI になる
  ◆ StartupUri の URI に影響する
◆ 自動で Main 関数や初期化処理を作る場合はリソースがないときは LoadComponent が実行されない
◆ リソースの有無で相対パスのベースが変わることがあるので絶対パスにしておいたほうがいい

WPF のアプリケーションでメインウィンドウが急に表示できなくなりました
変更したところはそういった問題が起きなそうな部分だけだったので原因見つけるのにすごく苦労しました

リソース追加で動かなくなる

プロジェクトの構成はこんなフォルダ階層です
App.config
WpfApp1.csproj
src\
App.xaml
App.xaml.cs
MainWindow.xaml
MainWindow.xaml.cs

App.xaml では MainWindow を StartupUri に指定しています

<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
StartupUri="src/MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

この状態だと問題なく起動できて MainWindow を開けます

ここから Resources に適当にリソースを追加します

<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:lib="clr-namespace:ClassLibrary1;assembly=ClassLibrary1"
StartupUri="src/MainWindow.xaml">
<Application.Resources>
<lib:XXXConverter x:Key="XXXConverter" />
</Application.Resources>
</Application>

namespace ClassLibrary1
{
public class XXXConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

中身は何でもいいので未実装の例外を出すだけの Converter にしました
ライブラリを読み込む作りにしましたが 別の DLL ではなく同じプロジェクト内に XXXConverter を作ってリソースに追加しても一緒です

追加後にリビルドして起動しようとしたらこんなエラーが出るようになります

リソース 'src/src/mainwindow.xaml' を検索できません。


src が 2 回ついています
src にいるときに 「src/mainwindow.xaml」 を指定したような動きです
相対パスのベースとなる場所が変わってるみたいです
ということは 「mainwindow.xaml」 だけを指定するか 「/」 からパスを始めれば大丈夫そうです

とりあえず StartupUri を / から始めてみると動くようになりました
StartupUri="/src/MainWindow.xaml"

初期化処理が変わる

どうしてリソース追加で相対パスが変わるのかわからなかったので少し調べてみました

App.InitializeCompnent をリソースがあるときとないときで比べてみます
このメソッドはビルド時に自動で作られるもので App.g.i.cs という名前のファイルに含まれています
プロジェクトの obj フォルダの中を見たり VisualStudio のクラス内メソッド一覧で InitializeComponent へジャンプするなどで確認できます


[リソースなし]
public void InitializeComponent() {

#line 6 "..\..\..\src\App.xaml"
this.StartupUri = new System.Uri("src/MainWindow.xaml", System.UriKind.Relative);

#line default
#line hidden
}

[リソースあり]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;

#line 6 "..\..\..\src\App.xaml"
this.StartupUri = new System.Uri("src/MainWindow.xaml", System.UriKind.Relative);

#line default
#line hidden
System.Uri resourceLocater = new System.Uri("/WpfApp1;component/src/app.xaml", System.UriKind.Relative);

#line 1 "..\..\..\src\App.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);

#line default
#line hidden
}

StartupUri は同じですが リソースがあるときだけ resourceLocater というのをロードしています
これが原因みたいです

LoadComponent

自動でいろいろされる部分の他の影響を受けてないことを確認するために もうちょっと試してみます

App.xaml のビルドアクションを Page に変更して StartupUri も XAML の指定からなくします

<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<Application.Resources>
</Application.Resources>
</Application>

この状態で Main 関数に自分でこう指定します

public partial class App : Application
{
[System.STAThreadAttribute()]
static public void Main()
{
var startup_uri = new System.Uri("src/MainWindow.xaml", System.UriKind.Relative);
var resource_locater = new System.Uri("/WpfApp1;component/src/app.xaml", System.UriKind.Relative);

var app = new App();
LoadComponent(app, resource_locater);
app.StartupUri = startup_uri;
app.Run();
}
}

これだと動かなくて LoadComponent の行をコメントアウトすれば動くようになります
LoadComponent を実行するとそこで指定した URI が App のベースの URI と設定されてるみたいです

App.xaml の場所とリソースのありなしを気にしなくていいように 「/」 から始まるパスを指定しておくのがよさそうです