2017年6月1日木曜日

VB プログラムから開く に対応するには

前から気になっていた事があります。
殆ど出番はなさそうなのですが、 プログラムから開く の問題です。
そうですね。 引数を受け取る様にしなければ ... 。
最近、 WPF で書く事が多いので、それを元に書いて見ますが、 Form でも基本は同じです。


ある拡張子のファイルに対いて、自分で書いたものが大活躍と言うケースは少ないと思います。
大抵は、既成のものが大活躍で、その隙間を埋める形で、自分のものが働いてくれています。

ですから、 Windows Explorer で表示される 「プログラムから開く」 「Open With... 」 はあまり関係無い のですが ... 。
仮に、 普通 (?) の形で書いたプログラムは、
大抵の場合、 その中でボタン等を用いてファイルを選択し開いたり、場合によっては、ドラッグドロップ実装したりしますね。
ドラッグドロップ実装 とは言え、多いケースは 受け手側 です。
そして、それらは、プログラムが起動されて やっと 機能する様になります。
そのままでは、この 「プログラムから開く」 は機能しません。
プログラム自体は開きますが ... 。
つまり、 プログラムが開かれた時点で、既に、ファイルが選択された状態になっていなければいけないからですね。


答えは簡単。
起動時に 引数 を与えれば 良いのですね。

引数の中身は ファイル群 です。


半角スペース区切りの文字列が渡されるのですね。
半角 Double Quotation Mark " を前後に入れてファイルパスの中にある 半角スペース を区別したりする
のを経験なさっていると思いますが、
「プログラムから開く」に関しては、これは不要、と言うか 反映済み です。
半角スペース混ざりのファイルパスも正確にハンドリングします。


では、 ちょっとした実験プログラムを書いて見ましょう。
WPF で Visual Studio 2017 Community の VB です。

コマンドライン引数 を 配列 で取得するのですが、
引数はファイル名である事を前提条件として、その拡張子と存否を確認して 取捨選択 をしています。
本来、 「プログラムから開く」 で存否確認は不要ですが、万一、実行時に手打ちの引数を与える事も考えて、こうしています。.
対象拡張子を変える場合、配列 fileExt() の値を書き換えて下さい。  小文字で です。.
Function ふたつですが、 ひとつはもう片方から呼ばれます。

    Private Function GetCmdArgs() As String()
        Dim value() As String = Nothing
        Dim valueList As New List(Of String)
        Dim cmdArgs() As String = Environment.GetCommandLineArgs
        Dim cmdArgTxt As New Text.StringBuilder
        If cmdArgs.Count > 1 Then
            For i As Integer = 1 To cmdArgs.Count - 1   'First Is AppFile
                If IsMatchTarget(cmdArgs(i)) Then
                    valueList.Add(cmdArgs(i))
                End If
            Next
        End If
        value = valueList.ToArray
        Return value
    End Function

    Private Function IsMatchTarget(fileName As String) As Boolean
        Dim fileExt() As String = {".jpg", ".jpeg"}   'Set Lower Char.
        Dim value As Boolean = False

        'Check File Exists (For Mistyping)
        If Not IO.File.Exists(fileName) Then
            Return value
            Exit Function
        End If

        'Check File's Extension
        For i As Integer = 0 To fileExt.Count - 1
            If IO.Path.GetExtension(fileName).ToLower = fileExt(i) Then
                value = True
                Exit For
            End If
        Next

        'Check ... If You Need, Add Here
        'If Then
        '
        'End If
        Return value
    End Function


実際には、こうして取得した引数を、実際のプログラムの中で所定場所に設定する必要もありますが ... 。
謂わば、ドラッグドロップの実装の時と同じ考えですね。


文末に全コードを掲載しますので、笑いながら、ご覧下さい。  お~、我が スパゲティ コード !!!.
尚、
そこでは、 ListBox のバインドをせず .Refresh で対応するもの(コメントアウト部分)と、
ObservableCollection でバインドしたものとを 併記 してあります。  xaml ではなく Code Behind で記述。.


さて、
GUI のものばかり書いて来た私ですが、 今回のケースは より コンソール アプリ 寄り です。
実行時の 引数 があるので、 デバック が ... 。
慣れない事 は 大変 です。

デバッグ(D) をクリックし、 展開された項目の一番下の xxx のプロパティ で開き、
コマンドライン引数 の所 に記述すれば、 デバッグ が可能です。
この時、ファイル名が半角スペースを含むパスの場合、 " で囲む事をお忘れなく。
でないと、 引数の区切りと認識され、まともにデバッグ出来ませんので。
構成を Debug から Release に替えると、 引数無しで実行されますので、留意を。.



おっと、 *1.
複数項目を引数に取れる様に書いたのですが、
実際に、 OS Explorer の 「 プログラムから開く 」 から実行させると、
複数ファイル選択時には これが表示されません。
ファイル関連付けをしても、 個別に複数のプログラムが立ち上がります。  これでは、メモリ圧迫が心配 ... 。.
つまり、 この書き方は、用途によっては、オーバースペック と言いう次第でして ... 。 *1.

しかし、 プログラムのアイコンをデスクトップに貼り付けると様相が変わります。  *2.
其処へ、複数のファイルをドロップする事で、当初の思惑通り、複数のものを指定したのと同じ状況を再現出来ます。
つまり、 ファイルがひとつなら 「 プログラムから開く 」 で、 複数ならドロップで、 と言う事になりそうです。  *2.



ではでは、お約束の 全コード。

WPF xaml
<Window x:Class="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:Try_WPF_OpenFromPrg_jpg"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="150*"/>
            <RowDefinition Height="150*"/>
        </Grid.RowDefinitions>

        <TextBox x:Name="TextBox0_Args" Margin="10,25,10,10" TextWrapping="Wrap" Text="TextBox" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
        <ListBox x:Name="listbox2_Sample" Margin="10,25,10,10" Grid.Row="1" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" Background="#FFFAFAFA"/>

        <TextBlock HorizontalAlignment="Left" Margin="10,9,0,0" TextWrapping="Wrap" Text="Exe Args" VerticalAlignment="Top"/>
        <TextBlock HorizontalAlignment="Left" Margin="10,9,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Exe Control" VerticalAlignment="Top"/>
        <CheckBox x:Name="CheckBox1_Add" Content="Keep Old Data" HorizontalAlignment="Left" Margin="150,0,0,0" Grid.Row="1" VerticalAlignment="Top"/>
        <CheckBox x:Name="CheckBox2_Items" Content="Exist Items" HorizontalAlignment="Left" Margin="279,0,0,0" Grid.Row="1" VerticalAlignment="Top"/>
        <Button x:Name="Button1_Do" Content="Add Args" HorizontalAlignment="Right" Margin="0,0,10,0" Grid.Row="1" VerticalAlignment="Top" Width="75"/>

    </Grid>
</Window>
Code VB.Net
Class MainWindow
    Dim listSrc As New List(Of String)                                      'If Not Bind
    Dim listSrcBind As New ObjectModel.ObservableCollection(Of String)      'If Bind

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        Dim targetArgs() As String = GetCmdArgs()
        Dim cmdArgTxt As New Text.StringBuilder

        For Each item In targetArgs
            cmdArgTxt.Append(item)
            cmdArgTxt.Append(vbCrLf)
        Next
        Me.TextBox0_Args.Text = cmdArgTxt.ToString

        'for Bind (WPF)
        listbox2_Sample.ItemsSource = listSrcBind
    End Sub

    Private Function GetCmdArgs() As String()
        Dim value() As String = Nothing
        Dim valueList As New List(Of String)
        Dim cmdArgs() As String = Environment.GetCommandLineArgs
        Dim cmdArgTxt As New Text.StringBuilder
        If cmdArgs.Count > 1 Then
            For i As Integer = 1 To cmdArgs.Count - 1   'First Is AppFile
                If IsMatchTarget(cmdArgs(i)) Then
                    valueList.Add(cmdArgs(i))
                End If
            Next
        End If
        value = valueList.ToArray
        Return value
    End Function

    Private Function IsMatchTarget(fileName As String) As Boolean
        Dim fileExt() As String = {".jpg", ".jpeg"}   'Set Lower Char.
        Dim value As Boolean = False

        'Check File Exists (For Mistyping)
        If Not IO.File.Exists(fileName) Then
            Return value
            Exit Function
        End If

        'Check File's Extension
        For i As Integer = 0 To fileExt.Count - 1
            If IO.Path.GetExtension(fileName).ToLower = fileExt(i) Then
                value = True
                Exit For
            End If
        Next

        'Check ... If You Need, Add Here
        'If Then
        '
        'End If
        Return value
    End Function

    Private Sub CheckBox2_Items_Checked(sender As Object, e As RoutedEventArgs) Handles CheckBox2_Items.Checked
        'listSrc.Add("Test1.jpg")
        'listSrc.Add("Test2.jpg")
        'listbox2_Sample.ItemsSource = listSrc
        'listbox2_Sample.Items.Refresh()

        'For Bind
        listSrcBind.Add("Test1.jpg")
        listSrcBind.Add("Test2.jpg")
    End Sub

    Private Sub CheckBox2_Items_Unchecked(sender As Object, e As RoutedEventArgs) Handles CheckBox2_Items.Unchecked
        'listSrc.Clear()
        'listbox2_Sample.ItemsSource = listSrc
        'listbox2_Sample.Items.Refresh()

        'For Bind
        listSrcBind.Clear()
    End Sub

    Private Sub Button1_Do_Click(sender As Object, e As RoutedEventArgs) Handles Button1_Do.Click
        If CheckBox1_Add.IsChecked Then     'Add Args

        Else                                'Replace To Args
            'listSrc.Clear()
            listSrcBind.Clear()
        End If

        Dim targetArgs() As String = GetCmdArgs()

        'listSrc.AddRange(targetArgs)
        'listbox2_Sample.ItemsSource = listSrc
        'listbox2_Sample.Items.Refresh()

        'For Bind
        For Each item In targetArgs
            listSrcBind.Add(item)
        Next
    End Sub
End Class



[2017/06/10] 顛末を追記 *1.
[2017/06/13] デスクトップアイコンとしての機能を追記  *2.

0 件のコメント:

コメントを投稿