お絵かきアプリを作ろう!

更新日: 2012 年 3 月 27 日

Zip Fileダウンロード (XPS、1.32 MB | PDF、1.19 MB | Zip、95.5 KB)


ここでは、あらかじめこちらで用意したプログラムを使って、お絵かきアプリを作ってみたいと思います。

Windows Phone は、パソコンと異なりキーボードを持っていません。その代り、マウスよりも直感的に操作できるタッチ パネルを持っています。Windows Phone でのお絵かきアプリは、まるで手帳にペンで絵を描くのと同じような感覚で利用することができます。

プロジェクトを作ってみよう!

まずは、Windows Phone のプロジェクトを作ってみましょう。Visual Studio を起動し、[ファイル] メニューから [新しいプロジェクト] を選択します。

[新しいプロジェクト] 画面

ここでは、使用する言語は Visual C# とします。
左側のペインで [Visual C#] の中にある [Silverlight for Windows Phone] テンプレートを選択します。
右側のペインにプロジェクトが表示されますので、[Windows Phone アプリケーション] を選択しましょう。
続いて画面下部にある、[名前] のところにプログラムの名前を付けましょう。ここでは、Oekaki という名前を付けてみます。名前を入力したら OK ボタンを押します。 続いて以下の画面が表示されます。

[新しい Windows Phone アプリケーション] 画面

ここでは、Windows Phone OS 7.1 を選択して OK ボタンを押します。
これでプロジェクトが完成しました。

ページのトップへ

プログラムをコピーしよう!

では、こちらで用意したプログラムをコピー アンド ペーストしましょう。
まずは、単純にお絵かきができるようなアプリを作ってみます。
現在は、以下のような画面になっていると思います。

MainPage.xaml の編集画面

この画面の左側のペインに表示されているのが、XAML (ザムル) と呼ばれるプログラムです。 次に [表示] メニューから [コード] を選択してみましょう。画面が切り替わり、別のプログラムが表示されます。こちらのプログラムは、C# という言語のプログラムです。

プログラムが表示されている部分のすぐ上には、MainPage.xaml.cs と表示されています。MainPage.xaml をクリックすると XAML のプログラムが表示され、MainPage.xaml.cs をクリックすると C# のプログラムが表示されます。このタブをクリックすることにより、いつでも XAML と C# の編集画面を切り替えることができます。
今後、XAML と C# のプログラムを修正する作業がありますが、その場合はこれらのタブを切り替えてコピー アンド ペーストしてください。

XAML と C# のタブによる編集画面切り替え

では、これら 2 つのプログラムを以下のものと丸ごと差し替えましょう。

[XAML (MainPage.xaml) のプログラム]

<phone:PhoneApplicationPage
    x:Class="oekaki.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResourcePhoneFontFamilyNormal}"
    FontSize="{StaticResourcePhoneFontSizeNormal}"
    Foreground="{StaticResourcePhoneForegroundBrush}"
    SupportedOrientations="Portrait"Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
    <!--LayoutRoot は、すべてのページ コンテンツが配置されるルートグリッドです-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <!--TitlePanel は、アプリケーション名とページ タイトルを格納します-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0"Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Windows Phone Sample" Style="{StaticResourcePhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Oekaki" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
        <!--ContentPanel - 追加コンテンツをここに入力します-->
        <Grid x:Name="ContentPanel" Grid.Row="1"Margin="12,0,12,0">
            <InkPresenter Name="inkPresenter1" Background="Transparent" MouseLeftButtonDown="inkPresenter1_MouseLeftButtonDown" MouseMove="inkPresenter1_MouseMove" />
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

[C# (MainPage.xaml.cs) のプログラム]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Windows.Ink;
namespace oekaki
{
    public partial class MainPage : PhoneApplicationPage
    {
        // コンストラクター
        public MainPage()
        {
            InitializeComponent();
        }
        private Stroke currentStroke;
        private void inkPresenter1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            currentStroke = new Stroke(e.StylusDevice.GetStylusPoints(inkPresenter1));
            currentStroke.DrawingAttributes.Color = Colors.White;
            currentStroke.DrawingAttributes.Width = 5;
            currentStroke.DrawingAttributes.Height = 5;
            inkPresenter1.Strokes.Add(currentStroke);
        }
        private void inkPresenter1_MouseMove(object sender, MouseEventArgs e)
        {
            currentStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkPresenter1));
        }
    }
}

ページのトップへ

お絵かきアプリを実行しよう!

これで簡単なお絵かきプログラムは完成です。実行してみましょう。
[デバッグ] メニューから [デバッグ開始] を選択します。すぐにエミュレーター (Windows Phone Emulator) が起動します。しばらく待っていると以下のような画面が表示されます。エミュレーターが起動しないときは、Visual Studio のツール バーに [Windows Phone Device] という文字が表示されていないか確認します。もし表示されていたときは、その部分を [Windows Phone Emulator (JA)] に変更して、再度実行します。

エミュレーター起動画面

ここで、画面の中央付近をマウス クリックしたまま動かすと、絵を描くことができます。

エミュレーター上での描画画面

ページのトップへ

アプリケーション バー (ApplicationBar) に使うアイコンをコピーしよう!

続いて、お絵かきアプリにいろいろな機能を追加してみましょう。
ここでは、アプリケーション バーという Windows Phone 用のボタンを 4 つ表示し、それぞれに以下の機能を割り当てていきます。

  1. ペンと消しゴムを切り替える機能
  2. 画像を消去する機能
  3. 画像を保存する機能
  4. 背景画像を読み込む機能

まずは、ボタン アイコンを保存するフォルダーを作りましょう。
ソリューション エクスプローラーで、プロジェクト名 [oekaki] のところを右クリックして、[追加] [新しいフォルダー] を選択します。

ソリューション エクスプローラーで新しいフォルダーを追加

ここでフォルダー名は、icons とします。

次に、ボタンに必要なアイコン画像をこのフォルダーにコピーしましょう。
icons のフォルダーは、標準の場合以下になります。

C:\Users\ユーザー名\Documents\visual studio 2010\Projects\oekaki\oekaki\icons

icons フォルダーを右クリックして、[エクスプローラーで開く] を選択すると簡単です。

ボタンに必要なアイコン画像を新規フォルダーにコピー

ここにコピーするアイコン ファイルは、5 本あります。まず、appbar.erase.rest.png だけ、ここからダウンロードしてから icons フォルダーにコピーしてください。

残りの 4 本は、以下です。

  • appbar.edit.rest.png
  • appbar.folder.rest.png
  • appbar.save.rest.png
  • appbar.delete.rest.png

これらのファイルは、Windows のバージョンによって格納されている場所が異なります。以下のいずれかに入っていますので、ご利用のWindows バージョンを確認の上、4 つのアイコン ファイルを icons フォルダーにコピーしてください。

  • 64 ビット: C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Icons\dark
  • 32 ビット: C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Icons\dark

続いて、icons に先ほどコピーしたファイルをプロジェクトに追加していきます。 icons フォルダーで右クリックして、[追加] [既存の項目] を選択します。

プロジェクトにコピーしたファイルを追加

ここで、先ほどコピーしたアイコン ファイルを 5 つとも追加してください。追加が終わるとソリューション エクスプローラーは以下のようになります。

アイコン ファイル追加後のソリューション エクスプローラー画面

次に追加したアイコン ファイルを全て選択し、プロパティを以下のように変更します。
全て指定するには、先頭 (appbar.edit.rest.png) をクリックし、SHIFT キーを押しながら最後 (appbar.save.rest.png) をクリックします。 もしプロパティ ウィンドウが表示されていない場合は、[表示] メニューの [プロパティ ウィンドウ] を選択して表示してください。

  • ビルド アクション: コンテンツ
  • 出力ディレクトリにコピー: 新しい場合は、コピーする

アイコン ファイルのプロパティ画面

これで、アイコン ファイルの設定は完了です。

ページのトップへ

アプリケーション バーを表示してみよう!

アイコンの設定が終わりましたので、ここでアプリケーション バーを表示してみましょう。 また、XAML ファイルと C# ファイルを差し替えてください。

[XAML (MainPage.xaml) のプログラム]

<phone:PhoneApplicationPage
    x:Class="oekaki.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696"
    FontFamily="{StaticResourcePhoneFontFamilyNormal}"
    FontSize="{StaticResourcePhoneFontSizeNormal}"
    Foreground="{StaticResourcePhoneForegroundBrush}"
    SupportedOrientations="Portrait"Orientation="Portrait"
    shell:SystemTray.IsVisible="True" DataContext="{Binding}">
    <!--LayoutRoot は、すべてのページ コンテンツが配置されるルートグリッドです-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <!--TitlePanel は、アプリケーション名とページ タイトルを格納します-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0"Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Windows Phone Sample" Style="{StaticResourcePhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Oekaki" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
        <!--ContentPanel - 追加コンテンツをここに入力します-->
        <Grid x:Name="ContentPanel" Grid.Row="1"Margin="12,0,12,0">
            <InkPresenter Name="inkPresenter1" Background="Transparent" MouseLeftButtonDown="inkPresenter1_MouseLeftButtonDown" MouseMove="inkPresenter1_MouseMove" />
        </Grid>
    </Grid>
    <!--ApplicationBar の使用法を示すサンプルコード-->
    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True" x:Name="AppBar" >
            <shell:ApplicationBarIconButton IconUri="/icons/appbar.erase.rest.png" Text="Pen" x:Name="PenButtton" Click="PenButtton_Click" />
            <shell:ApplicationBarIconButton IconUri="/icons/appbar.delete.rest.png" Text="Clear" x:Name="ClearButton" Click="ClearButton_Click" />
            <shell:ApplicationBarIconButton IconUri="/icons/appbar.save.rest.png" Text="Save" x:Name="SaveButton" Click="SaveButton_Click" />
            <shell:ApplicationBarIconButton IconUri="/icons/appbar.folder.rest.png" Text="Load" x:Name="LoadButton" Click="LoadButton_Click" />
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>

[C# (MainPage.xaml.cs) のプログラム]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Windows.Ink;
namespace oekaki
{
    public partial class MainPage : PhoneApplicationPage
    {
        // コンストラクター
        public MainPage()
        {
            InitializeComponent();
        }
        private Stroke currentStroke;
        private void inkPresenter1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            currentStroke = new Stroke(e.StylusDevice.GetStylusPoints(inkPresenter1));
            currentStroke.DrawingAttributes.Color = Colors.White;
            currentStroke.DrawingAttributes.Width = 5;
            currentStroke.DrawingAttributes.Height = 5;
            inkPresenter1.Strokes.Add(currentStroke);
        }
        private void inkPresenter1_MouseMove(object sender, MouseEventArgs e)
        {
            currentStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkPresenter1));
        }
        private void ClearButton_Click(object sender, EventArgs e)
        {
            MessageBox.Show("消去ボタンが押されました");
        }
        private void PenButtton_Click(object sender, EventArgs e)
        {
            MessageBox.Show("ペンボタンが押されました");
        }
        private void SaveButton_Click(object sender, EventArgs e)
        {
            MessageBox.Show("保存ボタンが押されました");
        }
        private void LoadButton_Click(object sender, EventArgs e)
        {
            MessageBox.Show("読み込みボタンが押されました");
        }
    }
}

ページのトップへ

実行してみよう!

では、再度プログラムを実行してみてください。以下のようにボタンが表示されれば成功です。

お絵かきアプリ画面に操作ボタンが表示

まだこの段階では、お絵かきアプリの機能はありません。ボタンを押したときには仮のメッセージが表示されるようになっています。
以下は消去ボタンを押したときの画面です。

お絵かきアプリ画面に消去ボタンが表示

ページのトップへ

機能を追加しよう!

最後にボタンに対応した機能を追加していきましょう。
まず、4 つの機能のうち、背景画像を読み込む機能を利用する場合は、参照設定を追加する必要があります。
[プロジェクト] メニューから [参照の追加] を選択します。ここで、「参照の追加」ウィンドウが表示されますので、Microsoft.Xna.Framework を選択し、OK ボタンをクリックします。

[プロジェクト] メニューの [参照の追加] 画面

では、先ほどと同様に C# のプログラムを以下と差し替えてください。今回は、XAML は書き換えません。

[C# (MainPage.xaml.cs) のプログラム]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Windows.Ink;
using System.Windows.Media.Imaging;
using System.IO;
using Microsoft.Xna.Framework.Media;
using Microsoft.Phone.Tasks;
using Microsoft.Phone.Shell;
namespace oekaki
{
    public partial class MainPage : PhoneApplicationPage
    {
        // コンストラクター
        public MainPage()
        {
            InitializeComponent();
        }
        private Stroke currentStroke;
        private void inkPresenter1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (penMode == true)
            {
                currentStroke = new Stroke(e.StylusDevice.GetStylusPoints(inkPresenter1));
                currentStroke.DrawingAttributes.Color = Colors.White;
                currentStroke.DrawingAttributes.Width = 5;
                currentStroke.DrawingAttributes.Height = 5;
                inkPresenter1.Strokes.Add(currentStroke);
            }
        }
        private void inkPresenter1_MouseMove(object sender, MouseEventArgs e)
        {
            if (penMode == true)
            {
                currentStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkPresenter1));
            }
            else
            {
                StylusPointCollection erasePoints = e.StylusDevice.GetStylusPoints(inkPresenter1);
                StrokeCollection hitStrokes = inkPresenter1.Strokes.HitTest(erasePoints);
                foreach (Stroke hitStroke in hitStrokes)
                {
                    Stroke sliceStroke = new Stroke();
                    sliceStroke.DrawingAttributes = hitStroke.DrawingAttributes;
                    foreach (StylusPoint sp in hitStroke.StylusPoints)
                    {
                        sliceStroke.StylusPoints.Add(sp);
                        if (sliceStroke.HitTest(erasePoints))
                        {
                            sliceStroke.StylusPoints.RemoveAt(sliceStroke.StylusPoints.Count - 1);
                            sliceStroke.DrawingAttributes = hitStroke.DrawingAttributes;
                            inkPresenter1.Strokes.Add(sliceStroke);
                            sliceStroke = new Stroke();
                            sliceStroke.DrawingAttributes = hitStroke.DrawingAttributes;
                        }
                    }
                    inkPresenter1.Strokes.Add(sliceStroke);
                    inkPresenter1.Strokes.Remove(hitStroke);
                }
            }
        }
        private void ClearButton_Click(object sender, EventArgs e)
        {
            inkPresenter1.Strokes.Clear();
        }
        public bool penMode = true;
        private void PenButtton_Click(object sender, EventArgs e)
        {
            if (penMode == true)
            {
                penMode = false;
                ((ApplicationBarIconButton)this.ApplicationBar.Buttons[0]).IconUri = new Uri("/icons/appbar.edit.rest.png", UriKind.Relative);
            }
            else
            {
                penMode = true;
                ((ApplicationBarIconButton)this.ApplicationBar.Buttons[0]).IconUri = new Uri("/icons/appbar.erase.rest.png", UriKind.Relative);
            }
        }
        private void SaveButton_Click(object sender, EventArgs e)
        {
            WriteableBitmap bmp = new WriteableBitmap(inkPresenter1, null);
            MemoryStream stream = new MemoryStream();
            bmp.SaveJpeg(stream, bmp.PixelWidth, bmp.PixelHeight, 0, 80);
            using (MediaLibrary mediaLib = new MediaLibrary())
            {
                mediaLib.SavePicture("PaintTest", stream.ToArray());
            }
            MessageBox.Show("保存しました");
        }
        private void LoadButton_Click(object sender, EventArgs e)
        {
            PhotoChooserTask photo = new PhotoChooserTask();
            photo.Completed += new EventHandler<PhotoResult>(photo_Completed);
            photo.Show();
        }
        void photo_Completed(object sender, PhotoResult e)
        {
            if (e.TaskResult == TaskResult.OK)
            {
                BitmapImage bmp = new BitmapImage();
                bmp.SetSource(e.ChosenPhoto);
                ImageBrush imageBrush = new ImageBrush();
                imageBrush.Stretch = Stretch.UniformToFill;
                imageBrush.ImageSource = bmp;
                inkPresenter1.Background = imageBrush;
            }
        }
    }
}

ページのトップへ

機能を使ってみよう!

では、プログラムを実行して、機能を確認してみましょう。 まずは、ペンと消しゴムの切り替え機能です。
絵を描いてから、消しゴム ボタンをクリックしてください。消しゴム モードにすると、線を消すことができるのが確認できるはずです。

消しゴムモードで線の一部を消した画面

眉と口の一部を消してみました。
次に、ごみ箱ボタンを押してみましょう。画面がクリアされるはずです。
3 つ目のボタンは画像の保存ボタンです。このプログラムでは、ファイル名は固定で 1 つだけ保存できるようにしています。
絵を描いてから、画像の保存ボタンを押すと「保存しました」というメッセージが表示されます。

保存ボタンで画像を保存した画面

最後に背景画像の読み込みボタンです。このボタンをクリックすると、背景画像を選択する画面が表示されます。先ほど保存した画像も選択できるのが確認できるでしょうか?

背景画像を読み込んだ後に描画した画面

もちろん、背景画像を読み込んだあとに絵を描くこともできます。

ページのトップへ

おわりに

お絵かきアプリは正しく動作しましたか? Windows Phone では、絵を描くのに適したコントロールや、アプリケーション バー (ボタンのセット) のように、貼り付けるだけで開発できるための部品が揃っています。これらの部品を利用すると、簡単に高度なアプリケーションを作ることができます。
もし、プログラミングに興味が出てきましたら、Hello Worldアナログ時計などの他のコンテンツも是非作ってみてください。

ページのトップへ