提督業プラグインを作ってみよう(2)艦隊情報を出そう

1.艦これの情報を取ってこよう

KanColleViewer本家のソース を参考にして艦これの情報を取ってくるModelクラスを作ります。 ModelsフォルダにKanColleModel.csを作りましょう。 以下にクラスの中身を記載します。

public class KanColleModel : Livet.NotificationObject, IDisposableHolder
{
   #region singleton

    public static KanColleModel Current { get; } = new KanColleModel();

   #endregion

   #region Fleets変更通知プロパティ
    private IEnumerable<Fleet> _Fleets;

    public IEnumerable<Fleet> Fleets
    {
        get
        { return _Fleets; }
        set
        {
            if (_Fleets == value)
                return;
            _Fleets = value;
            RaisePropertyChanged(nameof(Fleets));
        }
    }
   #endregion

   #region IsRegistered変更通知プロパティ
    private bool _IsRegistered = false;  //念のためこうしとく

    public bool IsRegistered
    {
        get
        { return _IsRegistered; }
        set
        {
            if (_IsRegistered == value)
                return;
            _IsRegistered = value;
            RaisePropertyChanged(nameof(IsRegistered));
        }
    }
   #endregion

    private readonly LivetCompositeDisposable compositeDisposable = new LivetCompositeDisposable();
    public ICollection<IDisposable> CompositeDisposable => this.compositeDisposable;

    /// <summary>
    /// 外からインスタンスが作れないようにコンストラクタをprivateにする
    /// </summary>
    private KanColleModel()
    {
        KanColleClient.Current
            .Subscribe(nameof(KanColleClient.IsStarted), this.RegisterHomeportListener, false)
            .AddTo(this);
    }

    /// <summary>
    /// 艦これが始まったときに一度だけ呼ばれます
    /// </summary>
    private void RegisterHomeportListener()
    {
        if (this.IsRegistered) return;

        var client = KanColleClient.Current;
        //Organization.Fleetsをチェックして、変化があったらUpdateFleetsを呼ぶ
        client.Homeport.Organization
        .Subscribe(nameof(Organization.Fleets), () => this.UpdateFleets(client.Homeport.Organization))
        .AddTo(this);

        this.IsRegistered = true;    //開始フラグを立てます
    }

    /// <summary>
    /// 艦隊に変化があったときに呼ばれる
    /// </summary>
    /// <param name="organization">organization</param>
    private void UpdateFleets(Organization organization)
    {
        //艦隊に変化があったらKanColleModel.Fleetsを更新する
        this.Fleets = organization.Fleets.Values;
    }

    public void Dispose()
    {
        this.compositeDisposable.Dispose();
    }
}

名前空間やusingは自力で何とかしましょう

2.ViewModelを実装しよう

ViewModelsフォルダにInformationViewModel.cs、FleetViewModel.cs、ShipViewModel.csを追加します。実装は以下に記載します。

InformationViewModel.cs(メインのViewModel)

public class InformationViewModel : ViewModel
{
    //艦これの情報はここからみんな持ってきます
    private KanColleModel Kancolle { get; } = KanColleModel.Current;

    private PropertyChangedEventListener listener;

    /// <summary>
    /// 艦隊情報
    /// </summary>
    public FleetViewModel[] Fleets { get; private set; }

    public InformationViewModel()
    {
        listener = new PropertyChangedEventListener(Kancolle);
        //艦これの艦隊情報の変化を監視して、変化があったらUpdateFleets()を呼ぶ
        listener.RegisterHandler(() => Kancolle.Fleets, (s, e) => UpdateFleets());

        this.CompositeDisposable.Add(listener);
    }

    /// <summary>
    /// 艦隊情報が更新されたときに呼ばれる
    /// </summary>
    private void UpdateFleets()
    {
        var fleets = this.Kancolle.Fleets;

        if (fleets == null) return;

        Fleets = fleets.Select(f => new FleetViewModel(f)).ToArray();
        RaisePropertyChanged(nameof(Fleets));
    }
}

FleetViewModel.cs(艦隊情報のViewModel)

public class FleetViewModel : ViewModel
{
   #region Name変更通知プロパティ
    private string _Name;

    public string Name
    {
        get
        { return _Name; }
        set
        {
            if (_Name == value)
                return;
            _Name = value;
            RaisePropertyChanged(nameof(Name));
        }
    }
   #endregion

    //艦隊に所属する艦娘の情報
    public ShipViewModel[] Ships { get; private set; }

    private PropertyChangedEventListener listener;

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="f">f</param>
    public FleetViewModel(Fleet f)
    {

        Name = f.Name;  //艦隊名をセット
        Update(f.Ships);
        
        listener = new PropertyChangedEventListener(f);
        //艦隊の艦娘たちの変化を監視して、変化があったらUpdate()を呼ぶ
        listener.RegisterHandler(() => f.Ships, (s, e) => Update(f.Ships));
        //艦隊名の変化を監視して、変化があったら艦隊名を更新する
        listener.RegisterHandler(() => f.Name, (s, e) => Name = f.Name);
        this.CompositeDisposable.Add(listener);
    }

    /// <summary>
    /// 艦隊に所属する艦娘の状態が変わったら呼ばれる
    /// </summary>
    /// <param name="ships">ships</param>
    protected virtual void Update(IEnumerable<Ship> ships)
    {
        this.Ships = ships.Select(s => new ShipViewModel(s)).ToArray();

        RaisePropertyChanged(nameof(Ships));
    }
}

ShipViewModel.cs(単体の艦娘のViewModel) KanColleViewer本家のShipViewModel.cs を参考にします。

public class ShipViewModel : ViewModel
{
   #region Ship変更通知プロパティ
    private Ship _Ship;

    public Ship Ship
    {
        get
        { return _Ship; }
        set
        {
            if (_Ship == value)
                return;
            _Ship = value;
            RaisePropertyChanged(nameof(Ship));
        }
    }
   #endregion

    public ShipViewModel(Ship s)
    {
        this.Ship = s;
    }
}

3.Viewにバインディングしよう

一度ソリューションをビルドして、コンパイルエラーが出たらつぶしましょう。このサイトではC#WPFの基本的なことは解説しません。インターネットは広大なので、助けを求めれば、一人くらいは相談に乗ってくれる人がいるでしょう。

コンパイルエラーが出なくなったら、Information.xamlの中身を実装していきましょう。以下に記載したソースをコピペ改変して自分のプロジェクトに合わせていきましょう。 Information.xaml(メインのユーザーコントロール

<UserControl 
    x:Class="プロジェクト名.Views.Information"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:プロジェクト名.ViewModels"
    mc:Ignorable="d" 
    d:DesignHeight="600" d:DesignWidth="400">
    <UserControl.Resources>
        <!-- 艦隊データテンプレート -->
        <DataTemplate x:Key="Fleet" DataType="{x:Type vm:ShipViewModel}">
            <StackPanel Orientation="Horizontal" DataContext="{Binding Ship}">
                <TextBlock TextWrapping="Wrap">
                    <Run Text="{Binding Info.Name, Mode=OneWay}"/>
                    <Run Text="{Binding Level, Mode=OneWay, StringFormat=Lv.\{0:D\}}"  />
                    <Run Text="{Binding Condition, Mode=OneWay, StringFormat=(\{0:D\})}"/>
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
    <StackPanel>
        <ItemsControl ItemsSource="{Binding Fleets}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Background="DarkGray" Foreground="LightGray">
                            <Run Text="{Binding Name}" />
                        </TextBlock>
                        <ItemsControl ItemsSource="{Binding Ships}" ItemTemplate="{DynamicResource Fleet}" >
                        </ItemsControl>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</UserControl>

全部実装したらビルドして、生成されたdllをKanColleViewerのPluginsフォルダに上書きコピーしてKanColleViewerを起動すると、以下の画像のようなウインドウが出てくるはずです。

f:id:reniris:20180906231829p:plain

艦隊名と、艦隊に所属している艦娘の名前、レベル、Cond値が表示されます。 このままでもCond値チェックは出来ますが、デザインがイケてなかったり、ウインドウを閉じたらKanColleViewerを再起動しないといけなかったりと、まだまだですね。 これらの問題は次回以降解決していきます。