20210227のC#に関する記事は11件です。

ASP.NET Core MVC Areaを利用する

何でエリアを利用するかについては省きます。

バージョン

.net 5.0
Microsoft Visual Studio Community 2019 Version 16.8.6

フォルダー構成

例えアプリにPersonnelとTreasurerという二つの論理グループがあるとします、エリアを利用するとフォルダー構造は次のようになります。
*プロジェクト名
 |-Areas
  |-Personnel
   |-Controllers
    |-HomeController.cs
   |-Models
    |-HomeModel.cs
   |-Views
    |-Home
     |-Index.cshtml
     |-About.cshtml
  |-Treasurer
   |-Controllers
    |-HomeController.cs
   |-Models
    |-HomeModel.cs
   |-Views
    |-Home
     |-Index.cshtml
     |-About.cshtml
 |-Controllers
 |-Models
 |-Views
 |-wwwroot

エリアを利用する時の構造は一般的にこうなっているですが、MVCの規約ではViewファイルのみこの構造に従う必要がある、ModelとControllerは従わなくても問題がない、外だしは可能ということです。

エリアを作成

プロジェクトで右クリック⇒追加⇒新規スキャフォールディングアイテム
image.png

MVCエリアを選んで追加、その後エリア名を入力して作成する
image.png

エリアを作成した後の実際の構造は次のようになります
image.png

エリアルートを追加

Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
       //ルーティングのルールによって、エリアを持つルートはエリアを持たないルート具体的なので、前に配置する必要がある
        endpoints.MapControllerRoute(
            name: "areas",
            pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

詳細なエリアルーティングはここから参照

上記コードの「{area:exists}」ではルートが必ず一つのエリアと一致しなければならないという制約を掛けている
詳細なルーティング制約はここから参照

エリアのコントローラー作成

HomeController.cs
namespace WebAppForDotNetCore5.Areas.Personnel.Controllers
{
    //エリア属性によって、該当コントローラーを指定のエリアと関連付ける
    [Area("Personnel")]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

_ViewStart.cshtml と _ViewImports.cshtml

この二ファイルの影響先はファイルを配置しているフォルダー及びその下級フォルダーのみ、なのでエリアじゃなく普通のViewsフォルダーにあるこの二ファイルはAreasに対しては影響を与えることはできない。
ViewsとAreasの両方に対して影響を与えたい場合はアプリケーションのルートフォルダ(Startup.csを含むフォルダ)にそのファイルを配置もしくはコピーすること。

_ViewStart.cshtmlの詳細はここから参照
_ViewImports.cshtmlの詳細はここから参照

Areasのフォルダー名変更

既定のエリアフォルダー「Areas」を変更することはできます

Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        //既存のエリアビューの検出ルールをクリア
        options.AreaViewLocationFormats.Clear();
        //新しいエリアビューの検出ルールを登録する
        options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/{1}/{0}.cshtml");
        options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/Shared/{0}.cshtml");
        options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
    });

    services.AddControllersWithViews();
}

Area属性の応用

前述のようにエリアとコントローラーを関連付けるため、ントローラーにArea属性を定義する必要が有ります、もしそのエリアに沢山のコントローラーがあったらそれ全部を定義する必要が有ります。そうなると問題が発生し易くなります。

Microsoft.AspNetCore.Mvc.Core.dll
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
public class AreaAttribute : Microsoft.AspNetCore.Mvc.Routing.RouteValueAttribute

AreaAttributeクラスの定義から見るとArea属性が継承を許されていることがわかる、つまり親にArea属性を設定するとそのクラスを継承した子にもArea属性が適用されます。よってエリアコントローラーは次のように変更可能

PersonnelBaseController.cs
namespace WebAppForDotNetCore5.Areas.Personnel.Controllers
{
    //親コントローラーにArea属性を設定
    //Controllerが抽象クラスなので、PersonnelBaseControllerは抽象にしてもしなくても良いです
    [Area("Personnel")]
    public abstract class PersonnelBaseController: Controller
    {
    }
}
HomeController.cs
namespace WebAppForDotNetCore5.Areas.Personnel.Controllers
{
    //親コントローラーにエリア属性を設定しているので、該当子コントローラーには設定しなくてもPersonnelエリアと関連付けている
    public class HomeController : PersonnelBaseController
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

まとめ

エリアのビューの検出順序
  • /Areas/<Area-Name>/Views/<Controller-Name>/<Action-Name>.cshtml
  • /Areas/<Area-Name>/Views/Shared/<Action-Name>.cshtml
  • /Views/Shared/<Action-Name>.cshtml
  • /Pages/Shared/<Action-Name>.cshtml
長くなるので、エリア毎に認証スキーム設定に関しては別の記事でまとめる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#-WPF】ListViewの項目を選択しメソッドを実行する

目的

以下の機能を持つUIを作る。ButtonをクリックしたらCheckマークがついたListViewの行の情報を取得しメソッドを実行する。
image.png

前提知識

・WPFの基礎
・オブジェクト指向
・リスト
・自動プロパティ

方針

1.ListViewの中にGridViewを入れ表を追加
2.ListViewにチェックボックスを追加
3.ListviewのヘッダーにCheckBoxを追加し、チェックした場合全Itemがチェックされる。アンチェックした場合、全Itemを解除する。
4.ListViewの項目を選択しメソッドを実行する

1.ListViewの中にGridViewを入れ表を追加

1.UI

image.png

ボタンをクリックすると表を表示するメソッドを実行
image.png

1.XAML

ListViewとListViewにデータを追加するボタンを配置します。
ListViewの中にGridViewを入れることで行と列に値を配置できる表を作成できます。

<Window x:Class="Listview0.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:Listview0"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Margin="0,67,-0.4,0">
        <Button Content="Button" HorizontalAlignment="Left" Margin="21,-41,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
        <ListView Margin="0,0,9.6,0">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="項目" Width="200" DisplayMemberBinding="{Binding items}"/>
                    <GridViewColumn Header="項目1" Width="200" DisplayMemberBinding="{Binding items1}"/>
                    <GridViewColumn Header="項目2" Width="200" DisplayMemberBinding="{Binding items2}"/>
                    <GridViewColumn Header="項目3" Width="200" DisplayMemberBinding="{Binding items3}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

1.メインのソースコード

namespaceの中にMainWindowクラスとデータを格納するListViewItemsクラスを作ります。
Button_Clickの中でデータを格納するリストを作りAddメソッドでデータを入れていきます。

using System;
using System.Collections.Generic;
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.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

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

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            List<ListViewItems> items = new List<ListViewItems>();
            items.Add(new ListViewItems() { items = "000", items1 = "111", items2 = "222", items3 = "333" });
            items.Add(new ListViewItems() { items = "aaa", items1 = "bbb", items2 = "ccc", items3 = "ddd" });
            items.Add(new ListViewItems() { items = "AAA", items1 = "BBB", items2 = "CCC", items3 = "DDD" });
            ListViewName.ItemsSource = items;
        }
    }

    public class ListViewItems
    {
        public string items { get; set; }
        public string items1 { get; set; }
        public string items2 { get; set; }
        public string items3 { get; set; }
    }
}

2.ListViewにチェックボックスを追加

image.png

2-1

2-1ではXAMLでチェックボックスを定義。

2-1.XAML

<Window x:Class="Listview_checkbox.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:Listview_checkbox"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Margin="-1,65,0.6,0">
        <Button Content="Button" HorizontalAlignment="Left" Margin="21,-41,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
        <ListView Name="ListViewName">
            <ListView.View>
                <GridView>

                    <!--GridViewColumnCheckBoxを追加する-->
                    <GridViewColumn >
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox Margin="5,0" IsChecked="true"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                    <GridViewColumn Header="項目1" Width="200" DisplayMemberBinding="{Binding items1}"/>
                    <GridViewColumn Header="項目2" Width="200" DisplayMemberBinding="{Binding items2}"/>
                    <GridViewColumn Header="項目3" Width="200" DisplayMemberBinding="{Binding items3}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

2-1.XAML解説

ListViewの1列目に表示されるCheckBoxを追加します。
DataTemplateは列のセルの書式を設定するために使用されます。

<GridViewColumn.CellTemplate>
<DataTemplate>
//ここにCheckBoxを入れる
 </DataTemplate>
</GridViewColumn.CellTemplate>

CheckBoxでIsCheckedをtrueにすると初期状態でCheckBoxにチェックが入ります。falseではチェックは入りません。

<CheckBox Margin="5,0" IsChecked="true"/>

2-1.MainWindowのソースコード

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

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            List<ListViewItems> items = new List<ListViewItems>();
            items.Add(new ListViewItems() { /*items = "000",*/ items1 = "111", items2 = "222", items3 = "333" });
            items.Add(new ListViewItems() { /*items = "aaa",*/ items1 = "bbb", items2 = "ccc", items3 = "ddd" });
            items.Add(new ListViewItems() { /*items = "AAA",*/ items1 = "BBB", items2 = "CCC", items3 = "DDD" });
            ListViewName.ItemsSource = items;
        }
    }

    public class ListViewItems
    {
        //public string items { get; set; }
        public string items1 { get; set; }
        public string items2 { get; set; }
        public string items3 { get; set; }
    }
}

今回は1列目にチェックボックスを追加するためitemsをコメントアウトします。他の列には前回と同じデータを入れます。

2-2

2-1では全てのチェックボックスの初期値をXMALで指定しましたが、2-2ではコードバインディング、MainWindowで指定します。

2-2XAML

<Window x:Class="Listview_checkbox.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:Listview_checkbox"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Margin="-1,65,0.6,0">
        <Button Content="Button" HorizontalAlignment="Left" Margin="21,-41,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
        <ListView Name="ListViewName">
            <ListView.View>
                <GridView>

                    <!--最初のGridViewColumnCheckBocを追加する-->
                    <GridViewColumn >
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox Margin="5,0" IsChecked="{Binding Selected}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                    <GridViewColumn Header="項目1" Width="200" DisplayMemberBinding="{Binding items1}"/>
                    <GridViewColumn Header="項目2" Width="200" DisplayMemberBinding="{Binding items2}"/>
                    <GridViewColumn Header="項目3" Width="200" DisplayMemberBinding="{Binding items3}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

解説

 <CheckBox Margin="5,0" IsChecked="{Binding Selected}"/>

IsCheckedをバインディングし、Selectedを変数にします。
MainWindowでSelectedを定義します。

2-2MainWindow

using System;
using System.Collections.Generic;
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.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

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

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            List<ListViewItems> items = new List<ListViewItems>();
            items.Add(new ListViewItems() { /*items = "000",*/ Selected = true, items1 = "111", items2 = "222", items3 = "333" });
            items.Add(new ListViewItems() { /*items = "aaa",*/ Selected = false, items1 = "bbb", items2 = "ccc", items3 = "ddd" });
            items.Add(new ListViewItems() { /*items = "AAA",*/ Selected = true, items1 = "BBB", items2 = "CCC", items3 = "DDD" });
            ListViewName.ItemsSource = items;
        }
    }

    public class ListViewItems
    {
        //public string items { get; set; }
        public bool Selected { get; set; }
        public string items1 { get; set; }
        public string items2 { get; set; }
        public string items3 { get; set; }
    }
}

解説

  public bool Selected { get; set; }

Selectedにtrueかfalseを与えるboolとします。
ボタンクリックではListViewItems()内において1,3列目をtrueと2列目にはfalseとしました。

3.ListviewのヘッダーにCheckBoxを追加し、チェックした場合全Itemがチェックされる。アンチェックした場合、全Itemを解除する。

UIにListViewのヘッダーにCheckBoxを追加し全ItemのCheckとUnCheckを行います。
コードが長くなるので2段階に分けて説明します。
1段階目はListViewコレクションの全要素をセレクトする、2段階目でセレクトした全要素のCheckBoxのCheckを変更していきます。

3-1. UI 段階1

まずListViewコレクションの要素をCastつまり青い選択された状態にする。
ListViewのヘッダーにあるCheckBoxをCheckすると全要素がセレクトされる。一方、CheckBoxがCheckされた状態でCheckを外すと全Itemのセレクトが外れるようにします。
image.png

3-1.XAML

<Window x:Class="Listview_checkbox.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:Listview_checkbox"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Margin="-1,65,0.6,0">
        <Button Content="Button" HorizontalAlignment="Left" Margin="21,-41,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
        <ListView Name="ListViewName">
            <ListView.View>
                <GridView>

                    <!--最初の列にCheckBocを追加する-->
                    <GridViewColumn >
                        <GridViewColumn.Header>
                            <CheckBox Name="CheckBox_PendingListAll" Checked="chkSelectAll_Checked" Unchecked="chkSelectAll_Unchecked" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </GridViewColumn.Header>
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox Margin="5,0" IsChecked="{Binding Selected}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                    <GridViewColumn Header="項目1" Width="200" DisplayMemberBinding="{Binding items1}"/>
                    <GridViewColumn Header="項目2" Width="200" DisplayMemberBinding="{Binding items2}"/>
                    <GridViewColumn Header="項目3" Width="200" DisplayMemberBinding="{Binding items3}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>


3-1. MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;


namespace Listview_checkbox
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //ListView
            List<ListViewItems> items = new List<ListViewItems>();
            items.Add(new ListViewItems() {  Selected = true, items1 = "111", items2 = "222", items3 = "333" });
            items.Add(new ListViewItems() {  Selected = false, items1 = "bbb", items2 = "ccc", items3 = "ddd" });
            items.Add(new ListViewItems() {  Selected = true, items1 = "BBB", items2 = "CCC", items3 = "DDD" });
            ListViewName.ItemsSource = items;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {

        }

        private void chkSelectAll_Checked(object sender, RoutedEventArgs e)
        {   
            foreach (ListViewItems item in ListViewName.ItemsSource)
            {
                ListViewName.SelectedItems.Add(item);
            }
        }

        private void chkSelectAll_Unchecked(object sender, RoutedEventArgs e)
        {
                foreach (ListViewItems item in ListViewName.ItemsSource)
                {
                    ListViewName.SelectedItems.Remove(item);
                }
        }
    }

    public class ListViewItems
    {
        public bool Selected { get; set; }
        public string items1 { get; set; }
        public string items2 { get; set; }
        public string items3 { get; set; }
    }
}


3-1. 解説

ListViewのヘッダーにCheckBoxを配置し、CheckとUnCheckしたときのメソッドを追加する。

<GridViewColumn.Header>
  <CheckBox Name="CheckBox_PendingListAll" Checked="chkSelectAll_Checked" Unchecked="chkSelectAll_Unchecked" HorizontalAlignment="Center"        VerticalAlignment="Center"/>
</GridViewColumn.Header>

chkSelectAll_Checkedが実行されれば,
foreachを用いListViewName.ItemsSourceにある全要素取り出し、 ListViewName.SelectedItems.Add(item)を実行。
chkSelectAll_Uncheckedが実行されればforeachを用いListViewName.ItemsSourceにある全要素取り出し、 ListViewName.SelectedItems.Remove(item);を実行。

3-2. UI 段階2

ListViewヘッダーのCheckBoxをCheckすると全アイテムがCheckされる、UnCheckされる
image.png

3-2. XAML

<Window x:Class="Listview_checkbox.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:Listview_checkbox"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Margin="-1,65,0.6,0">
        <Button Content="Button" HorizontalAlignment="Left" Margin="21,-41,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
        <ListView Name="ListViewName" SelectionChanged="ListViewName_SelectionChanged">
            <ListView.View>
                <GridView>

                    <!--最初の列にCheckBocを追加する-->
                    <GridViewColumn >
                        <GridViewColumn.Header>
                            <CheckBox Name="CheckBox_PendingListAll" Checked="chkSelectAll_Checked" Unchecked="chkSelectAll_Unchecked" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </GridViewColumn.Header>
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox Margin="5,0" IsChecked="{Binding Selected}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                    <GridViewColumn Header="項目1" Width="200" DisplayMemberBinding="{Binding items1}"/>
                    <GridViewColumn Header="項目2" Width="200" DisplayMemberBinding="{Binding items2}"/>
                    <GridViewColumn Header="項目3" Width="200" DisplayMemberBinding="{Binding items3}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>


3-2.MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;

namespace Listview_checkbox
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //ListView
            List<ListViewItems> items = new List<ListViewItems>();
            items.Add(new ListViewItems() { /*items = "000",*/ Selected = true, items1 = "111", items2 = "222", items3 = "333" });
            items.Add(new ListViewItems() { /*items = "aaa",*/ Selected = false, items1 = "bbb", items2 = "ccc", items3 = "ddd" });
            items.Add(new ListViewItems() { /*items = "AAA",*/ Selected = true, items1 = "BBB", items2 = "CCC", items3 = "DDD" });
            ListViewName.ItemsSource = items;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {

        }

        private void chkSelectAll_Checked(object sender, RoutedEventArgs e)
        {

            foreach (ListViewItems item in ListViewName.ItemsSource)
            {

                ListViewName.SelectedItems.Add(item);
            }
        }

        private void chkSelectAll_Unchecked(object sender, RoutedEventArgs e)
        {

            foreach (ListViewItems item in ListViewName.ItemsSource)
            {

                ListViewName.SelectedItems.Remove(item);
            }

        }

        private void ListViewName_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                //------------
                ListViewItems user = (ListViewItems)e.AddedItems[0];
                ListViewItem lvi = (ListViewItem)ListViewName.ItemContainerGenerator.ContainerFromItem(user);
                CheckBox chkBx = FindVisualChild<CheckBox>(lvi);
                if (chkBx != null)
                    chkBx.IsChecked = true;
                //------------              
            }
            else // Remove Select All chkBox
            {
                ListViewItems user = (ListViewItems)e.RemovedItems[0];
                ListViewItem lvi = (ListViewItem)ListViewName.ItemContainerGenerator.ContainerFromItem(user);
                CheckBox chkBx = FindVisualChild<CheckBox>(lvi);
                if (chkBx != null)
                    chkBx.IsChecked = false;
            }

        }

        public static T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        return (T)child;
                    }

                    T childItem = FindVisualChild<T>(child);
                    if (childItem != null) return childItem;
                }
            }
            return null;
        }


    }

    public class ListViewItems
    {
        //public string items { get; set; }
        public bool Selected { get; set; }
        public string items1 { get; set; }
        public string items2 { get; set; }
        public string items3 { get; set; }
    }
}


3-2. 解説

ListViewでSelectとされた要素に変更を与えるメソッド ListViewName_SelectionChangedをXAMLとMainWindowに追加します。
すでに段階1で記載したchkSelectAll_checkedとchkSelectAll_Uncheckedで全アイテムのSelect変更が行われその次に ListViewName_SelectionChangedが実行されます。

<ListView Name="ListViewName" SelectionChanged="IvTestItems_SelectionChanged">
            <ListView.View>

ListViewName_SelectionChangedの実装をもってすべてのCheckBoxをCheckまたはUnCheckします。

4. ListViewの項目を選択しメソッドを実行する

4. XMALコード

前回のコードに対してButton_Clickを追加

<Button Content="Button" HorizontalAlignment="Left" Margin="21,-41,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>

4. 全体のロジックコード

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Listview_checkbox
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //ListView
            List<ListViewItems> items = new List<ListViewItems>();
            items.Add(new ListViewItems() { /*items = "000",*/ Selected = true, items1 = "111", items2 = "222", items3 = "333" });
            items.Add(new ListViewItems() { /*items = "aaa",*/ Selected = false, items1 = "bbb", items2 = "ccc", items3 = "ddd" });
            items.Add(new ListViewItems() { /*items = "AAA",*/ Selected = true, items1 = "BBB", items2 = "CCC", items3 = "DDD" });
            ListViewName.ItemsSource = items;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            foreach (ListViewItems item in ListViewName.ItemsSource)
            {
                //item.Selectedがtrueの場合
                if (item.Selected)
                {
                    Excute(item.items1);
                }
            }
        }

        private void Excute(string items1)
        {
            Console.WriteLine(items1);
        }

        private void chkSelectAll_Checked(object sender, RoutedEventArgs e)
        {

            foreach (ListViewItems item in ListViewName.ItemsSource)
            {

                ListViewName.SelectedItems.Add(item);
            }
        }

        private void chkSelectAll_Unchecked(object sender, RoutedEventArgs e)
        {

            foreach (ListViewItems item in ListViewName.ItemsSource)
            {

                ListViewName.SelectedItems.Remove(item);
            }

        }

        private void ListViewName_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                //------------
                ListViewItems user = (ListViewItems)e.AddedItems[0];
                ListViewItem lvi = (ListViewItem)ListViewName.ItemContainerGenerator.ContainerFromItem(user);
                CheckBox chkBx = FindVisualChild<CheckBox>(lvi);
                if (chkBx != null)
                    chkBx.IsChecked = true;
                //------------              
            }
            else // Remove Select All chkBox
            {
                ListViewItems user = (ListViewItems)e.RemovedItems[0];
                ListViewItem lvi = (ListViewItem)ListViewName.ItemContainerGenerator.ContainerFromItem(user);
                CheckBox chkBx = FindVisualChild<CheckBox>(lvi);
                if (chkBx != null)
                    chkBx.IsChecked = false;
            }

        }

        public static T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        return (T)child;
                    }

                    T childItem = FindVisualChild<T>(child);
                    if (childItem != null) return childItem;
                }
            }
            return null;
        }


    }

    public class ListViewItems
    {
        //public string items { get; set; }
        public bool Selected { get; set; }
        public string items1 { get; set; }
        public string items2 { get; set; }
        public string items3 { get; set; }
    }
}

4. 解説

ロジックコードで追加した部分です。

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            foreach (ListViewItems item in ListViewName.ItemsSource)
            {
                //item.Selectedがtrueの場合
                if (item.Selected)
                {
                    Excute(item.items1);
                }
            }
        }

        private void Excute(string items1)
        {
            Console.WriteLine(items1);
        }

クリックメソッドを実行すると、foreach文が実行されます。
foreachでは「XMALのNameを"ListViewName"としたListView」の生成に使用されるコレクションから各要素を一つずつ取り出します。
各要素はListViewItemsクラスで定義された変数を保持しておりListViewの行に対応しています。
ここではitem1変数を引数としてメソッドExcuteを実行しています。
例えばListViewの項目1をテスト名にして、Checkしたテスト名だけを選択してメソッドを実行するなんてこともできるようになります。

まとめ

以下の機能を持つWPFについてもまとめました
1.ListViewの中にGridViewを入れ表を追加
2.ListViewにチェックボックスを追加
3.ListviewのヘッダーにCheckBoxを追加し、チェックした場合全Itemがチェックされる。アンチェックした場合、全Itemを解除する。
4.ListViewの項目を選択しメソッドを実行する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#とMySQLを接続してCRUD操作をしてみる

環境

Windous10 HOME
Visual Studio 2019 Community
MySQL 8.0

やったこと

学習のためにVisual Studio C#でMySQLのCRUD操作ができるコンソールアプリを作成する。
※CRUD → CREATE、READ、UPDATE、DELETE

参考

https://qiita.com/yuzooho/items/5d608f3b84a2037cada0
https://qiita.com/kazumi75kitty/items/9953bb84edd7717bc90a
https://mikeda.hatenablog.com/entry/20090419/1240123566

上記を参考にさせていただきました。
ありがとうございます。

環境設定

  1. 「新しいプロジェクトの作成」からコンソールアプリ(.NET Framework)を選択して作成します。
    ※この時「C#」「Windows」「コンソール」と書かれている方選ばないと後々MySQLと接続できなくなります。

スクリーンショット (4).png

  1. 「参照」を右クリックして「参照の追加」を選択します。

スクリーンショット (8).png

参照マネージャーの検索でMySQLと入力して「MySql.data」にチェックして「OK」を選択する。

スクリーンショット (9).png

  1. MySQLのコマンドを起動してデータベース「test_database」を作成します。
MySQL
CREATE DATABASE test_database;

「test_database」が作成されているか確認します。

MySQL
SHOW DATABASES;

スクリーンショット (7).png

「test_database」が作成されました。

コードを記述

  1. CRUD操作を行うコードを記述していきます。
Program.cs
using System;
using MySql.Data.MySqlClient;// MySQLを使用


namespace MySQLテスト
{
    class Program
    {

        // MySQLへの接続情報
        private static readonly string server = "localhost";
        private static readonly string database = "test_database";//使用するデータベース
        private static readonly string user = "root";//ユーザー名
        private static readonly string pass = "password";//インストール時に設定したパスワード
        private static readonly string charset = "utf8";

        // MySQLへの接続
        private static readonly string connectionString = string.Format("Server={0};Database={1};Uid={2};Pwd={3};Charset={4}", server, database, user, pass, charset);

        // テーブル作成SQL
        private static readonly string MysqlTable = "test_table";//テーブル名
        private static readonly string CreateTableSql = "CREATE TABLE IF NOT EXISTS " + database + "." + MysqlTable + "(id int, name varchar(32), address varchar(32))";

        //データ挿入SQL
        private static readonly string InsertData = "(1, 'Tarou', 'Shinjuku'), (2, 'Hiroshi', 'Shibuya'), (3, 'Yumi', 'Setagaya'), (4, 'miku', 'Chiyoda')";//挿入するデータ
        private static readonly string InsertTableSql = "INSERT INTO " + MysqlTable + " (id, name, address) VALUES " + InsertData;

        //データ抽出用SQL
        private static readonly string SelectSql = "SELECT * FROM " + MysqlTable;//全てのカラムのデータを出力

        //データ更新用SQL
        private static readonly string UpdateSql = "UPDATE " + MysqlTable + " SET address = 'Meguro' WHERE name = 'Hiroshi'";//「Hiroshi」のaddressを「Meguro」に変更

        //データ削除用SQL
        private static readonly string DeleteSql = "DELETE FROM " + MysqlTable + " WHERE id = 4";//「id」が4のレコードを削除


        static void Main(string[] args)
        {
            try
            {
                // コネクションオブジェクトとコマンドオブジェクトの生成
                using (var connection = new MySqlConnection(connectionString))
                using (var command = new MySqlCommand())
                {
                    // コネクションオープン
                    connection.Open();


                    // テーブル作成SQLを実行
                    command.Connection = connection;
                    command.CommandText = CreateTableSql;
                    Console.WriteLine("テーブル「" + MysqlTable + "」を作成しました");
                    command.ExecuteNonQuery();
                    Console.WriteLine("続行するには何かキーを押してください");
                    Console.ReadKey();


                    // データ挿入用SQLを実行
                    command.Connection = connection;
                    command.CommandText = InsertTableSql;
                    Console.WriteLine("テーブル「" + MysqlTable + "」にデータ「" + InsertData + "」を挿入しました");
                    command.ExecuteNonQuery();
                    Console.WriteLine("続行するには何かキーを押してください");
                    Console.ReadKey();


                    // データ抽出SQLを実行
                    command.Connection = connection;
                    command.CommandText = SelectSql;
                    Console.WriteLine("テーブル「" + MysqlTable + "」のデータを出力します");

                    //カラム名を出力
                    MySqlDataReader reader = command.ExecuteReader();

                    string[] column = new string[reader.FieldCount];
                    for (int i = 0; i < reader.FieldCount; i++)
                        column[i] = reader.GetName(i);
                    Console.WriteLine(string.Join("\t", column));

                    //テーブルのデータを出力
                    while (reader.Read())
                    {
                        string[] row = new string[reader.FieldCount];
                        for (int i = 0; i < reader.FieldCount; i++)
                            row[i] = reader.GetString(i);
                        Console.WriteLine(string.Join("\t", row));
                    }
                    reader.Close();
                    Console.WriteLine("続行するには何かキーを押してください");
                    Console.ReadKey();


                    //データ更新SQL
                    command.Connection = connection;
                    command.CommandText = UpdateSql;
                    Console.WriteLine("ひろしの住所を目黒に変更しました");
                    command.ExecuteNonQuery();
                    Console.WriteLine("続行するには何かキーを押してください");
                    Console.ReadKey();


                    //データ削除SQL
                    command.Connection = connection;
                    command.CommandText = DeleteSql;
                    Console.WriteLine("idが4のデータを削除しました");
                    command.ExecuteNonQuery();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            Console.ReadKey();
        }
    }
}

テーブル作成、データ挿入・抽出・更新・削除をキー入力で進めるプログラムです。

MySQLを確認しながら実行していく

MySQLのコマンドで確認しながら実行していきます。
Visual Studioで「デバッグ」から「デバッグなしで開始」を選択します。

無題.png

実行するとテーブル「test_table」が作成されます。
MySQLで確認します。

MySQL
USE test_database;

「test_database」にデータベースを切り替えます。

MySQL
SHOW TABLES;

スクリーンショット (11).png

「test_table」が作成されました。
コンソールに戻って進めます。

スクリーンショット (15).png

データが挿入されます。
MySQLで確認します。

MySQL
SELECT * FROM test_table;

スクリーンショット (16).png

挿入されました。
コンソールに戻って進めます。

スクリーンショット (12).png

テーブルのデータを出力しました。
先ほどMySQLで確認したデータと同じです。
このまま進めます。

スクリーンショット (13).png

「Hiroshi」の「address」が「Meguro」に変更されているか確認します

スクリーンショット (17).png

変更されました。
コンソールに戻って進めます。

スクリーンショット (14).png

id = 4のレコードが削除されているか確認します。

スクリーンショット (18).png

削除されてます。

感想

今回はC#でMySQLを操作する基本を学びました。
確認しながら操作することで直感的に理解できたと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで行動分析 ~その4:反応率分化強化スケジュール編~

1. はじめに

  準備編 で作成した Operandum1 の Script を編集して、時間スケジュールを作成します。基本的なことは準備編で一通り解説しているので、本記事では Operandum1 の Script の解説のみとなります。また、今回も「オペランダムへの反応」→「得点上昇」と「強化オペランダムへの反応」→「得点上昇」の2つの場合を考慮して解説したいと思います。

2. 反応率分化強化スケジュールとは

 反応率分化強化スケジュールは、反応形成において反応を変容する手続きです。本記事では反応間時間 ( IRT ) を分化強化の対象とする反応型の分化強化スケジュールとして、低反応率分化強化スケジュール ( Differential reinforcement of low rate; DRL )と高反応率分化強化スケジュール ( Differential reinforcement of high rate; DRH )の2つを解説します。

2.1. DRLとは

 DRLとは、「IRTがある値を超えた後の最初の反応に強化子が随伴する(坂上・井上, 2018, pp175)」強化スケジュールです。もしその値以内で反応した場合は、再びその時点からIRTが計測されます。無反応での一定時間の経過が強化の必要条件となっています。
 Unityで作成する場合は、弁別刺激点灯時に限り反応はいつでも受けつけるけれど、スケジュール値 ( x sec ) を超過してから反応しなければ強化子を得られない ( あるいは Ramp が点灯しない ) ようにすれば良いです。また、スケジュール値 ( x sec ) 経過中に反応してしまうと時間を0にリセットするよう設定します。

2.2. DRHとは

 DRHとは、DRLの逆にあたる強化スケジュールで(坂上・井上, 2018)、スケジュール値よりも短い時間での反応に強化子が随伴する強化スケジュールです(Ono & Iwabuchi, 1997)。もしスケジュール値以上で反応した場合は、再びその時点からIRTが計測されます。
 Unityで作成する場合は、弁別刺激点灯時に限り反応はいつでも受けつけるけれど、反応がスケジュール値 ( x sec ) 未満でなければ強化子を得られない ( あるいは Ramp が点灯しない ) ようにすれば良いです。また、スケジュール値 ( x sec ) 以降に反応してしまうと時間を0にリセットするよう設定します。

3. DRL

3.1. 「オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Operandum1_Script : MonoBehaviour
{
    int Point = 1;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;

    //New
    float time;
    public float DRL;
    public AudioClip Operandum1SE;


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }


    // New
    void Update()
    {
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                if (time > DRL)
                {
                    audioSource.PlayOneShot(PointSE);
                    CountText.text = "Point : " + Point.ToString();
                    Point += 1;
                    time = 0;
                }
                else
                {
                    time = 0;
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • float time; ... float型の変数 time を宣言
  • public float DRL; ... public な float型の変数 DRL を宣言
    → Editor上ではDRLのスケジュール値を入れてください
  • public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
    → Editor上では Operandum1 に反応したときに鳴るSEを入れてください

Update()

  • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
    • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
    • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
      • 効果音( Operandum1SE )が鳴る
      • 「if (time > DRL)」の処理 ... timeがDRLのスケジュール値を超過したときの処理
        • 効果音( PointSE )が鳴る
        • 得点が1点上昇( Point += 1; )
        • time を0にリセット
          → 得点が上昇すると時間がリセットされ、もう一度時間を計測しはじめる
          → 弁別刺激が点灯している間、DRLが走り続ける
      • 「else」の処理 ... timeがDRLのスケジュール値を超過したとき以外の処理
        • time を0にリセット
          → オペランダムに反応すると時間がリセットされ、もう一度時間を計測しはじめる

3.2. 「強化オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Operandum1_Script : MonoBehaviour
{
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public AudioClip Operandum1SE;
    AudioSource audioSource;
    GameObject Ramp;
    Ramp_Script Ramp_Script;

    //New
    float time;
    public float DRL;


    void ResetTime()
    {
        time = 0;
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        Ramp = GameObject.Find("Ramp");
        Ramp_Script = Ramp.GetComponent<Ramp_Script>();
    }


    void Update()
    {
        // New_1
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);

                // New_2
                if (time > DRL)
                {
                    Ramp_off.SetActive(false);
                    Ramp_on.SetActive(true);
                    Invoke("ResetTime", Ramp_Script.ReinforceableTime);
                }

                else
                {
                    time = 0;
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
            Ramp_off.SetActive(true);
            Ramp_on.SetActive(false);
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • float time; ... float型の変数 time を宣言
  • public float DRL; ... public な float型の変数 DRL を宣言
    → Editor上ではDRLのスケジュール値を入れてください

Update()

  • New_1
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
  • New_2
    • 「if (time > DRL)」の処理 ... timeがDRLのスケジュール値を超過したときの処理
      • Ramp_off を非アクティブ化、Ramp_on をアクティブ化
        → 疑似的に強化可能ランプの点灯を表現
      • Invoke("ResetTime", Ramp_Script.ReinforceableTime)
        → 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセット
      • 「else」の処理 ... timeがDRLのスケジュール値を超過したとき以外の処理
        • time を0にリセット
          → オペランダムに反応すると時間がリセットされ、もう一度時間を計測しはじめる

4. DRH

4.1. 「オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Operandum1_Script : MonoBehaviour
{
    int Point = 1;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;

    //New
    float time;
    public float DRH;
    public AudioClip Operandum1SE;


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }


    // New
    void Update()
    {
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                if (time < DRH)
                {
                    audioSource.PlayOneShot(PointSE);
                    CountText.text = "Point : " + Point.ToString();
                    Point += 1;
                    time = 0;
                }
                else
                {
                    time = 0;
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • float time; ... float型の変数 time を宣言
  • public float DRH; ... public な float型の変数 DRH を宣言
    → Editor上ではDRHのスケジュール値を入れてください
  • public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
    → Editor上では Operandum1 に反応したときに鳴るSEを入れてください

Update()

  • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
    • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
    • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
      • 効果音( Operandum1SE )が鳴る
      • 「if (time < DRH)」の処理 ... timeがDRHのスケジュール値未満だったときの処理
        • 効果音( PointSE )が鳴る
        • 得点が1点上昇( Point += 1; )
        • time を0にリセット
          → 得点が上昇すると時間がリセットされ、もう一度時間を計測しはじめる
          → 弁別刺激が点灯している間、DRHが走り続ける
      • 「else」の処理 ... timeがDRHのスケジュール値未満だったとき以外の処理
        • time を0にリセット
          → オペランダムに反応すると時間がリセットされ、もう一度時間を計測しはじめる

4.2. 「強化オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Operandum1_Script : MonoBehaviour
{
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public AudioClip Operandum1SE;
    AudioSource audioSource;
    GameObject Ramp;
    Ramp_Script Ramp_Script;

    //New
    float time;
    public float DRH;


    void ResetTime()
    {
        time = 0;
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        Ramp = GameObject.Find("Ramp");
        Ramp_Script = Ramp.GetComponent<Ramp_Script>();
    }


    void Update()
    {
        // New_1
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);

                // New_2
                if (time < DRH)
                {
                    Ramp_off.SetActive(false);
                    Ramp_on.SetActive(true);
                    Invoke("ResetTime", Ramp_Script.ReinforceableTime);
                }

                else
                {
                    time = 0;
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
            Ramp_off.SetActive(true);
            Ramp_on.SetActive(false);
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • float time; ... float型の変数 time を宣言
  • public float DRH; ... public な float型の変数 DRH を宣言
    → Editor上ではDRHのスケジュール値を入れてください

Update()

  • New_1
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
  • New_2
    • 「if (time < DRH)」の処理 ... timeがDRHのスケジュール値未満だったときの処理
      • Ramp_off を非アクティブ化、Ramp_on をアクティブ化
        → 疑似的に強化可能ランプの点灯を表現
      • Invoke("ResetTime", Ramp_Script.ReinforceableTime)
        → 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセット
      • 「else」の処理 ... timeがDRHのスケジュール値未満だったとき以外の処理
        • time を0にリセット
          → オペランダムに反応すると時間がリセットされ、もう一度時間を計測しはじめる

4.3. x sec 間に n

 「2.2. DRHとは」では、DRHはスケジュール値よりも短い時間での反応に強化子が随伴する強化スケジュールと書きましたが、x sec 間に n 回反応すると強化子が随伴する場合もあるようです(e.g., 小野, 1994)。
 Unityで作成する場合は、弁別刺激点灯時に限り反応はいつでも受けつけるけれど、スケジュール値 ( x sec ) 未満かつ n 回反応しなければ強化子を得られない ( あるいは Ramp が点灯しない ) ようにすれば良いです。また、スケジュール値 ( x sec ) 以降に反応したり n 回に到達していない場合は、時間を0にリセットするよう設定します。

4.3.1. 「オペランダムへの反応」→「得点上昇」

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Operandum1_Script : MonoBehaviour
{
    int Point = 1;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;

    //New
    float time;
    int Counter = 0;
    public float DRHTime;
    public float DRHTimes;
    public AudioClip Operandum1SE;


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }


    // New
    void Update()
    {
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                if (time < DRHTime)
                {
                    if (Counter == DRHTimes)
                    {
                        audioSource.PlayOneShot(PointSE);
                        CountText.text = "Point : " + Point.ToString();
                        Point += 1;
                        time = 0;
                        Counter = 0;
                    } 

                }

                else
                {
                    time = 0;
                    Counter = 0;
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
            Counter = 0;
        }
    }
}

4.3.2. 「強化オペランダムへの反応」→「得点上昇」

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Operandum1_Script : MonoBehaviour
{
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public AudioClip Operandum1SE;
    AudioSource audioSource;
    GameObject Ramp;
    Ramp_Script Ramp_Script;

    //New
    float time;
    public float DRHTime;
    public float DRHTimes;
    int Counter = 0;


    void ResetTime()
    {
        time = 0;
        Counter = 0;
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        Ramp = GameObject.Find("Ramp");
        Ramp_Script = Ramp.GetComponent<Ramp_Script>();
    }


    void Update()
    {
        // New_1
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            Debug.Log(time);
            if (Input.GetKeyDown(KeyCode.X))
            {
                audioSource.PlayOneShot(Operandum1SE);
                Counter += 1;

                // New_2
                if (time < DRHTime)
                {
                    if (Counter == DRHTimes)
                    {
                        Ramp_off.SetActive(false);
                        Ramp_on.SetActive(true);
                        Invoke("ResetTime", Ramp_Script.ReinforceableTime);
                    } 
                }
                else
                {
                    time = 0;
                    Counter = 0;
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
            Counter = 0;
            Ramp_off.SetActive(true);
            Ramp_on.SetActive(false);
        }
    }
}

5. 最後に

 「反応率分化強化スケジュール」の中の、「低反応率分化強化(Differential Reinforcement of Low rates)」と「低反応率分化強化(Differential Reinforcement of High rates)」をUnityで作る方法の解説を行いました。コードや用語等で間違っている点があれば、ご指摘いただけると幸いです。

参考URL

・NumPy, randomで様々な種類の乱数の配列を生成
https://note.nkmk.me/python-numpy-random/

・Unity で CSV ファイルを読み込む方法
https://note.com/macgyverthink/n/n83943f3bad60

・【Unity】C#の基本構文『for』
http://kimama-up.net/unity-for/

引用文献

小野 浩一 (1994). 迷信行動と言語. 駒澤社会学研究, 26, 59-84.

Ono K, & Iwabuchi K. (1997). Effects of histories of differential reinforcement of response rate on variable-interval responding. Journal of the Experimental Analysis of Behavior. 67(3), 311–322.

坂上 貴之・井上 雅彦 (2018). 行動分析学──行動の科学的理解をめざして── 有斐閣

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】文字列、整数の型切り替え

一括でまとめいているサイトがなかったのでメモ。

単なる変換

文字列 → 整数

string s = "1";
int i = int.Parse(s);

整数 → 文字列

int i = 2;
string s = i.ToString();

リストの変換

文字列 → 整数

参考サイト:stackoverflow - How to convert List to List?

List<string> ls = new List<string>() {"1", "2", "3"};
List<int> li = ls.Select(int.Parse).ToList();

整数 → 文字列

参考サイト:stackoverflow - Cast List to List in .NET 2.0

List<int> li = new List<int>(new int[] { 1,2,3 } );
List<string> ls = li.ConvertAll<string>(x => x.ToString());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】Null許容のDatetimeを比較してBooleanを返すメソッド【Nullable.Compare】

Nullを許容したDateTime型変数を検索条件として、Linqの.Where内で使いたかったためメソッド作りました。

公式でNullable.Compareというメソッドがあるのですが、返却値がInt型で使いづらかったのでBoolean型にしておきました。

メソッド

using System;

//date1がdate2より遅い日付の場合True
public bool CompareNullableDatetime(DateTime? date1, DateTime? date2)
{
     int result = Nullable.Compare(date1, date2);
     return result > 0;
}

仕様

DateTimeが非Nullの場合
返却値 引数1 引数2 補足
TRUE 2021/02/27 00:00:00 2021/01/27 00:00:00 引数1が引数2より遅い時刻 
FALSE 2021/01/27 00:00:00 2021/02/27 00:00:00 引数1が引数2より早い時刻 
FALSE 2021/02/27 00:00:00 2021/02/27 00:00:00 引数1と引数2が同じ時刻
DateTimeがNullを含む場合
返却値 引数1 引数2
TRUE 2021/02/27 00:00:00 Null
FALSE Null 2021/02/27 00:00:00
FALSE Null Null

Nullable.Compareに関して補足

公式ドキュメント

Nullable.Compareの仕様

覚え方的には、Nullを「0」だと思って「引数1-引数2」をする感覚です。

DateTimeが非Nullの場合
返却値 引数1 引数2 補足
0より大きい値 2021/02/27 00:00:00 2021/01/27 00:00:00 引数1が引数2より遅い時刻 
0より小さい値 2021/01/27 00:00:00 2021/02/27 00:00:00 引数1が引数2より早い時刻 
0 2021/02/27 00:00:00 2021/02/27 00:00:00 引数1と引数2が同じ時刻
DateTimeがNullを含む場合
返却値 引数1 引数2
0より大きい値 2021/02/27 00:00:00 Null
0より小さい値 Null 2021/02/27 00:00:00
0 Null Null

より大きい値、より小さい値ってなんやねん

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで行動分析 ~その3:時間スケジュール編~

1. はじめに

  準備編 で作成した Operandum1 の Script を編集して、時間スケジュールを作成します。基本的なことは準備編で一通り解説しているので、本記事では Operandum1 の Script の解説のみとなります。また、今回も「オペランダムへの反応」→「得点上昇」と「強化オペランダムへの反応」→「得点上昇」の2つの場合を考慮して解説したいと思います。

2. 時間スケジュールとは

 時間スケジュール(Time schedule)とは、1つ前の強化子の提示から一定時間経過後に強化子が随伴する強化スケジュールです(坂上・井上, 2018)。一定時間 ( スケジュール値 ) が固定である場合は固定時間スケジュール ( Fixed Time schedule; FT ) 、平均するとスケジュール値になる場合は変動時間スケジュール ( Variable Time schedule; VT ) と呼びます。
 Unityで作成する場合は、スケジュール値 ( x sec ) に到達すると強化子が提示される ( あるいは Ramp が点灯する ) ようにすれば良いです。時間スケジュールのイメージ図を下に示します。下図の左は「オペランダムへの反応」→「得点上昇」を、下図の右は「強化オペランダムへの反応」→「得点上昇」を示しています。FTであれば x sec が常に一定となり、VTであれば x sec が毎回変動します。

3. FT

3.1. 「オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Operandum1_Script : MonoBehaviour
{
    int Point = 1;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;

    //New
    float time;
    public float FTTime;
    public AudioClip Operandum1SE;


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }


    // New
    void Update()
    {
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
            }

            if (time >= FTTime)
            {
                audioSource.PlayOneShot(PointSE);
                CountText.text = "Point : " + Point.ToString();
                Point += 1;
                time = 0;
            }
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • float time; ... float型の変数 time を宣言
  • public float FTTime; ... public な float型の変数 FTTime を宣言
    → Editor上ではFTのスケジュール値を入れてください
  • public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
    → Editor上では Operandum1 に反応したときに鳴るSEを入れてください

Update()

  • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
    • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
    • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
      • 効果音( Operandum1SE )が鳴る
    • 「if (time >= FTTime)」の処理 ... timeがFTのスケジュール値に到達したときの処理 * 効果音( PointSE )が鳴る * 得点が1点上昇( Point += 1; ) * time を0にリセット
      → 得点が上昇すると時間がリセットされ、もう一度時間を計測しはじめる
      → 弁別刺激が点灯している間、FTが走り続ける

3.2. 「強化オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Operandum1_Script : MonoBehaviour
{
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public AudioClip Operandum1SE;
    AudioSource audioSource;
    GameObject Ramp;
    Ramp_Script Ramp_Script;

    //New
    float time;
    public float FTTime;


    void ResetTime()
    {
        time = 0;
        CancelInvoke();
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        Ramp = GameObject.Find("Ramp");
        Ramp_Script = Ramp.GetComponent<Ramp_Script>();
    }


    void Update()
    {
        // New_1
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
            }

            // New_2
            if (time >= FTTime)
            {
                Ramp_off.SetActive(false);
                Ramp_on.SetActive(true);
                Invoke("ResetTime", Ramp_Script.ReinforceableTime);
            } 
        }

        if (Sd1_off.activeSelf)
        {
            time = 0;
            Ramp_off.SetActive(true);
            Ramp_on.SetActive(false);
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。ResetTime()に、「CancelInvoke();」が追加されているので注意してください。CancelInvoke()については、 こちら を参照してください。

変数の宣言

  • float time; ... float型の変数 time を宣言
  • public float FTTime; ... public な float型の変数 FTTime を宣言
    → Editor上ではFTのスケジュール値を入れてください

Update()

  • New_1
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
  • New_2
    • 「if (time >= FTTime)」の処理 ... timeがFTのスケジュール値に到達したときの処理
      • Ramp_off を非アクティブ化、Ramp_on をアクティブ化
        → 疑似的に強化可能ランプの点灯を表現
      • Invoke("ResetTime", Ramp_Script.ReinforceableTime)
        → 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセット

4. VT

4.1. Pythonで x sec のリストを作成する

 VTでは、FTとは異なり、x sec が一定ではなく変動します。この変動した値をUnity上で作成しても良いのですが、先にPythonで x sec のリストを作成してCsv形式で出力しておきます。その後、作成したCsv形式の x sec のリストをUnityで読み込みます。

4.1.1. x sec のリストを作成する関数

  x sec のリストを作成する関数は下記のとおりです。環境について、Pythonのバージョンは「Python 3.7.1」で、Jupyter Notebookを使用しています。

Python
import numpy as np

def variable(value, value_min, value_max, reinforcement):
    for i in range(100**100):
        random_ = np.random.randint(value_min, value_max, reinforcement)
        if random_.mean()==value:
            variable = random_
            break
    return variable
  • forの中の処理
    • スケジュール値の範囲 ( value_min から value_max まで ) の乱数(一様分布)を reinforcement 分作成して1次元の行列にする
      • 乱数生成については こちら を参照してください
    • ifの中の処理
      • random_ の平均値がスケジュール値と同じになった場合、variable に random_ を格納
      • variable に random_ を格納したらforループを中断
        → スケジュール値の範囲がよほど無茶なものでない限り、100の100乗回のforループは行われない

4.1.2. x sec のリストをCsvファイルに出力

Python
# 「_」には、value, range_min, range_max, reinforcementの値を入れてください
value, range_min, range_max, reinforcement = _, _, _, _
variable = variable(value, range_min, range_max, reinforcement)

# 「/」の前にデータの出力先を入れてください
np.savetxt('/Variable.csv', variable, delimiter=',') 

 作成したCsvファイルは、Assetの中のResourcesというファイルを作成して、その中に入れます。

4.2. 「オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;

public class Operandum1_Script : MonoBehaviour
{
    int Point = 1;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;

    //New
    bool first = true;
    float time;
    int i;
    int CsvCounter = 0;
    private List<string> CsvVariable = new List<string>();
    public AudioClip Operandum1SE;


    void Start()
    {
        audioSource = GetComponent<AudioSource>();

        //New_1
        TextAsset Csv = Resources.Load("Variable") as TextAsset;
        StringReader reader = new StringReader(Csv.text);
        while (reader.Peek() != -1)
        {
            string line = reader.ReadLine();
            string[] values = line.Split(',');

            // New_2
            for (i = 0; i < values.Length; i++)
            {
                CsvVariable.Add(values[i]);
            }
        }
    }


    void Update()
    {
        // New_3
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
            }
            // New_4
            if (first)
            {
                if (time >= int.Parse(CsvVariable[CsvCounter]))
                {
                    audioSource.PlayOneShot(PointSE);
                    CountText.text = "Point : " + Point.ToString();
                    Point += 1;
                    CsvCounter += 1;
                    time = 0;
                }
            }
        }
        if (Sd1_off.activeSelf)
        {
            time = 0;
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。5行目に「using System.IO;」が追加されているので注意してください。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • bool first = true; ... bool型の変数 first が true であることを宣言
  • float time; ... float型の変数 time を宣言
  • int i; ... int型の変数 i を宣言
  • int CsvCounter = 0; ... int型の変数 CsvCounter が 0 であることを宣言
  • private List CsvVariable = new List(); ... string型の List として CsvVariable を宣言
  • public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
    → Editor上では Operandum1 に反応したときに鳴るSEを入れてください

Start()

  • New_1
    • CsvファイルをUnityに読み込ませる
    • こちらの記事 とやっていることは全く同じで、詳しい解説も載っているのでここでは割愛します
  • New_2
    • 「for (i = 0; i < values.Length; i++)」の処理 ... 取得したCsvファイルの値を List の中に格納する処理
      • C# のforループの書き方については こちら を参照してください

Update()

  • New_3
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
  • New_4
    • 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
      • 「if (time >= int.Parse(CsvVariable[CsvCounter]))」の処理 ... timeがVTのスケジュール値に到達したときの処理
        • 効果音( PointSE )が鳴る
        • 得点が1点上昇( Point += 1; )
        • CsvCounterが1つ増加
          → 取得するList内の要素の順番を1つずらす
        • time を0にリセット
          → 得点が上昇すると時間がリセットされ、もう一度時間を計測しはじめる
          → 弁別刺激が点灯している間、VTが走り続ける

4.3. 「強化オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class Operandum1_Script : MonoBehaviour
{
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public AudioClip Operandum1SE;
    AudioSource audioSource;
    GameObject Ramp;
    Ramp_Script Ramp_Script;

    //New
    bool first = true;
    float time;
    int i;
    int CsvCounter = 0;
    private List<string> CsvVariable = new List<string>();


    void ResetTime()
    {
        time = 0;
        first = true;
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        Ramp = GameObject.Find("Ramp");
        Ramp_Script = Ramp.GetComponent<Ramp_Script>();

        //New_1
        TextAsset Csv = Resources.Load("Variable") as TextAsset;
        StringReader reader = new StringReader(Csv.text);
        while (reader.Peek() != -1)
        {
            string line = reader.ReadLine();
            string[] values = line.Split(',');

            // New_2
            for (i = 0; i < values.Length; i++)
            {
                CsvVariable.Add(values[i]);
            }
        }
    }


    void Update()
    {
        // New_3
        if (Sd1_on.activeSelf)
        {
            time += Time.deltaTime;
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
            }

            // New_4
            if (first)
            {
                if (time >= int.Parse(CsvVariable[CsvCounter]))
                {
                    Ramp_off.SetActive(false);
                    Ramp_on.SetActive(true);
                    CsvCounter += 1;
                    first = false;
                    Invoke("ResetTime", Ramp_Script.ReinforceableTime);
                }
            }

            if (Sd1_off.activeSelf)
            {
                time = 0;
                Ramp_off.SetActive(true);
                Ramp_on.SetActive(false);
            }
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update() → ResetTime()となります。5行目に「using System.IO;」が追加されているので注意してください。また、ResetTime()では「time = 0;」だけではなく「first = true;」や「CancelInvoke();」も書かれてあるので注意してください。CancelInvoke()については、 こちら を参照してください。
解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • bool first = true; ... bool型の変数 first が true であることを宣言
  • float time; ... float型の変数 time を宣言
  • int i; ... int型の変数 i を宣言
  • int CsvCounter = 0; ... int型の変数 CsvCounter が 0 であることを宣言
  • private List CsvVariable = new List(); ... string型の List として CsvVariable を宣言

Start()

  • New_1
    • CsvファイルをUnityに読み込ませる
    • こちらの記事 とやっていることは全く同じで、詳しい解説も載っているのでここでは割愛します
  • New_2
    • 「for (i = 0; i < values.Length; i++)」の処理 ... 取得したCsvファイルの値を List の中に格納する処理
      • C# のforループの書き方については こちら を参照してください

Update()

  • New_3
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
  • New_4
    • 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
      • 「if (time >= int.Parse(CsvVariable[CsvCounter]))」の処理 ... timeがVTのスケジュール値に到達したときの処理
        • Ramp_off を非アクティブ化、Ramp_on をアクティブ化
          → 疑似的に強化可能ランプの点灯を表現
        • CsvCounterが1つ増加
          → 取得するList内の要素の順番を1つずらす
        • first を false にする → 取得するList内の要素の順番が2つ以上ずれないようにする
        • Invoke("ResetTime", Ramp_Script.ReinforceableTime)
          → 強化可能ランプ点灯時から強化可能時間が経過すると、time を0にリセットして、first を true に戻す

5. 最後に

 「時間スケジュール(time schedule)」の中の、「固定時間スケジュール(Fixed Time schedule)」と「変動時間スケジュール(Variable Time schedule)」をUnityで作る方法の解説を行いました。コードや用語等で間違っている点があれば、ご指摘いただけると幸いです。

参考URL

・NumPy, randomで様々な種類の乱数の配列を生成
https://note.nkmk.me/python-numpy-random/

・Unity で CSV ファイルを読み込む方法
https://note.com/macgyverthink/n/n83943f3bad60

・【Unity】C#の基本構文『for』
http://kimama-up.net/unity-for/

・【Unity】Invokeの使い方!実行タイミングを自在に操ろう
https://www.sejuku.net/blog/83762

引用文献

坂上 貴之・井上 雅彦 (2018). 行動分析学──行動の科学的理解をめざして── 有斐閣

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで行動分析 ~その2:比率スケジュール編~

1. はじめに

  準備編 で作成した Operandum1 の Script を編集して、比率スケジュールを作成します。基本的なことは準備編で一通り解説しているので、本記事では Operandum1 の Script の解説のみとなります。また、今回も「オペランダムへの反応」→「得点上昇」と「強化オペランダムへの反応」→「得点上昇」の2つの場合を考慮して解説したいと思います。

2. 比率スケジュールとは

 比率スケジュール(Ratio schedule)とは、「強化子提示後に一定回数自発された反応に強化子が随伴する(坂上・井上, 2018, pp.173)」強化スケジュールです。一定回数 ( スケジュール値 ) が固定である場合は固定比率スケジュール ( Fixed Ratio schedule; FR ) 、平均するとスケジュール値になる場合は変動比率スケジュール ( Variable Ratio schedule; VR ) と呼びます。
 Unityで作成する場合は、弁別刺激点灯時に限り反応はいつでも受けつけるけれど、スケジュール値 ( x times ) に到達しなければ強化子を得られない ( あるいは Ramp が点灯しない ) ようにすれば良いです。比率スケジュールのイメージ図を下に示します。下図の左は「オペランダムへの反応」→「得点上昇」を、下図の右は「強化オペランダムへの反応」→「得点上昇」を示しています。FRであれば x times が常に一定となり、VRであれば x times が毎回変動します。

3. FR

3.1. 「オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Operandum1_Script : MonoBehaviour
{
    int Point = 1;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;

    //New
    int Counter = 0;
    public int FRTimes;
    public AudioClip Operandum1SE;


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }


    // New
    void Update()
    {
        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                Counter += 1;
                audioSource.PlayOneShot(Operandum1SE);
                if (Counter == FRTimes)
                {
                    audioSource.PlayOneShot(PointSE);
                    CountText.text = "Point : " + Point.ToString();
                    Point += 1;
                    Counter = 0;
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            Counter = 0;
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • int Counter; ... int型の変数 Counter を宣言
  • public int FRTimes; ... public な float型の変数 FRTimes を宣言
    → Editor上ではFRのスケジュール値を入れてください
  • public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
    → Editor上では Operandum1 に反応したときに鳴るSEを入れてください

Update()

  • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
    • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
      • 効果音( Operandum1SE )が鳴る
      • Counterのカウントが1増加
      • 「if (Counter == FR)」の処理 ... CounterがFRのスケジュール値に到達したときの処理
        • 効果音( PointSE )が鳴る
        • 得点が1点上昇( Point += 1; )
        • Counter を0にリセット
          → 得点が上昇すると回数がリセットされ、もう一度時間を計測しはじめる
          → 弁別刺激が点灯している間、FRが走り続ける

3.2. 「強化オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Operandum1_Script : MonoBehaviour
{
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public AudioClip Operandum1SE;
    AudioSource audioSource;
    GameObject Ramp;
    Ramp_Script Ramp_Script;

    //New
    int Counter = 0;
    public int FRTimes;

    void ResetTimes()
    {
        Counter = 0;
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        Ramp = GameObject.Find("Ramp");
        Ramp_Script = Ramp.GetComponent<Ramp_Script>();
    }


    void Update()
    {
        // New_1
        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                Counter += 1;

                // New_2
                if (Counter == FRTimes)
                {
                    Ramp_off.SetActive(false);
                    Ramp_on.SetActive(true);
                    Invoke("ResetTimes", Ramp_Script.ReinforceableTime);
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            Counter = 0;
            Ramp_off.SetActive(true);
            Ramp_on.SetActive(false);
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • int Counter; ... int型の変数 Counter を宣言
  • public int FRTimes; ... public な float型の変数 FRTimes を宣言
    → Editor上ではFRのスケジュール値を入れてください

Update()

  • New_1
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
        • Counterのカウントが1増加
  • New_2
    • 「if (Counter == FR)」の処理 ... CounterがFRのスケジュール値に到達したときの処理
      • Ramp_off を非アクティブ化、Ramp_on をアクティブ化
        → 疑似的に強化可能ランプの点灯を表現
      • Invoke("ResetTimes", Ramp_Script.ReinforceableTime)
        → 強化可能ランプ点灯時から強化可能時間が経過すると、Counter を0にリセット

4. VR

4.1. Pythonで x sec のリストを作成する

 VRでは、FRとは異なり、x times が一定ではなく変動します。この変動した値をUnity上で作成しても良いのですが、先にPythonで x times のリストを作成してCsv形式で出力しておきます。その後、作成したCsv形式の *x times * のリストをUnityで読み込みます。

4.1.1. x times のリストを作成する関数

  *x times * のリストを作成する関数は下記のとおりです。環境について、Pythonのバージョンは「Python 3.7.1」で、Jupyter Notebookを使用しています。

Python
import numpy as np

def variable(value, value_min, value_max, reinforcement):
    for i in range(100**100):
        random_ = np.random.randint(value_min, value_max, reinforcement)
        if random_.mean()==value:
            variable = random_
            break
    return variable
  • forの中の処理
    • スケジュール値の範囲 ( value_min から value_max まで ) の乱数(一様分布)を reinforcement 分作成して1次元の行列にする
      • 乱数生成については こちら を参照してください
    • ifの中の処理
      • random_ の平均値がスケジュール値と同じになった場合、variable に random_ を格納
      • variable に random_ を格納したらforループを中断
        → スケジュール値の範囲がよほど無茶なものでない限り、100の100乗回のforループは行われない

4.1.2. *x times * のリストをCsvファイルに出力

Python
# 「_」には、value, range_min, range_max, reinforcementの値を入れてください
value, range_min, range_max, reinforcement = _, _, _, _
variable = variable(value, range_min, range_max, reinforcement)

# 「/」の前にデータの出力先を入れてください
np.savetxt('/Variable.csv', variable, delimiter=',') 

 作成したCsvファイルは、Assetの中のResourcesというファイルを作成して、その中に入れます。

4.2. 「オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;

public class Operandum1_Script : MonoBehaviour
{
    int Point = 1;
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public Text CountText;
    public AudioClip PointSE;
    AudioSource audioSource;

    //New
    bool first = true;
    int Counter = 0;
    int i;
    int CsvCounter = 0;
    private List<string> CsvVariable = new List<string>();
    public AudioClip Operandum1SE;


    void Start()
    {
        audioSource = GetComponent<AudioSource>();

        //New_1
        TextAsset Csv = Resources.Load("Variable") as TextAsset;
        StringReader reader = new StringReader(Csv.text);
        while (reader.Peek() != -1)
        {
            string line = reader.ReadLine();
            string[] values = line.Split(',');

            // New_2
            for (i = 0; i < values.Length; i++)
            {
                CsvVariable.Add(values[i]);
            }
        }
    }


    void Update()
    {
        // New_3
        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                Counter += 1;

                // New_4
                if (first)
                {
                    if (Counter == int.Parse(CsvVariable[CsvCounter]))
                    {
                        audioSource.PlayOneShot(PointSE);
                        CountText.text = "Point : " + Point.ToString();
                        Point += 1;
                        CsvCounter += 1;
                        Counter = 0;
                    }
                }
            }
        }

        if (Sd1_off.activeSelf)
        {
            Counter = 0;
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update()となります。5行目に「using System.IO;」が追加されているので注意してください。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • bool first = true; ... bool型の変数 first が true であることを宣言
  • int Counter; ... int型の変数 Counter を宣言
  • int i; ... int型の変数 i を宣言
  • int CsvCounter = 0; ... int型の変数 CsvCounter が 0 であることを宣言
  • private List CsvVariable = new List(); ... string型の List として CsvVariable を宣言
  • public AudioClip Operandum1SE; ... public な AudioClip として Operandum1SE を宣言
    → Editor上では Operandum1 に反応したときに鳴るSEを入れてください

Start()

  • New_1
    • CsvファイルをUnityに読み込ませる
    • こちらの記事 とやっていることは全く同じで、詳しい解説も載っているのでここでは割愛します
  • New_2
    • 「for (i = 0; i < values.Length; i++)」の処理 ... 取得したCsvファイルの値を List の中に格納する処理
      • C# のforループの書き方については こちら を参照してください

Update()

  • New_3
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
        • Counterのカウントが1増加
  • New_4
    • 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
      • 「if (Counter == int.Parse(CsvVariable[CsvCounter]))」の処理 ... CounterがVRのスケジュール値に到達したときの処理
        • 効果音( PointSE )が鳴る
        • 得点が1点上昇( Point += 1; )
        • CsvCounterが1つ増加
          → 取得するList内の要素の順番を1つずらす
        • Coutner を0にリセット
          → 得点が上昇すると回数がリセットされ、もう一度時間を計測しはじめる
          → 弁別刺激が点灯している間、VRが走り続ける

4.3. 「強化オペランダムへの反応」→「得点上昇」

 Script の内容は下記のとおりです。ちなみに、「// New」の下に書かれてあるコードは、準備編の Operandum1 の Script にはなかったコードです。

Operandum1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class Operandum1_Script : MonoBehaviour
{
    public GameObject Sd1_off;
    public GameObject Sd1_on;
    public GameObject Ramp_off;
    public GameObject Ramp_on;
    public AudioClip Operandum1SE;
    AudioSource audioSource;
    GameObject Ramp;
    Ramp_Script Ramp_Script;

    //New
    bool first = true;
    int Counter = 0;
    int i;
    int CsvCounter = 0;
    private List<string> CsvVariable = new List<string>();


    void ResetTimes()
    {
        Counter = 0;
        first = true;
    }


    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        Ramp = GameObject.Find("Ramp");
        Ramp_Script = Ramp.GetComponent<Ramp_Script>();

        //New_1
        TextAsset Csv = Resources.Load("Variable") as TextAsset;
        StringReader reader = new StringReader(Csv.text);
        while (reader.Peek() != -1)
        {
            string line = reader.ReadLine();
            string[] values = line.Split(',');

            // New_2
            for (i = 0; i < values.Length; i++)
            {
                CsvVariable.Add(values[i]);
            }
        }
    }


    void Update()
    {
        // New_3
        if (Sd1_on.activeSelf)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                audioSource.PlayOneShot(Operandum1SE);
                Counter += 1;

                // New_4
                if (first)
                {
                    if (Counter == int.Parse(CsvVariable[CsvCounter]))
                    {
                        Ramp_off.SetActive(false);
                        Ramp_on.SetActive(true);
                        CsvCounter += 1;
                        first = false;
                        Invoke("ResetTimes", Ramp_Script.ReinforceableTime);
                    }
                }
            }

            if (Sd1_off.activeSelf)
            {
                Counter = 0;
                Ramp_off.SetActive(true);
                Ramp_on.SetActive(false);
            }
        }
    }
}

 このScriptの流れをざっくり書くと、変数の宣言 → Start() → Update() → ResetTime()となります。5行目に「using System.IO;」が追加されているので注意してください。また、ResetTime()では「time = 0;」だけではなく「first = true;」も書かれてあるので注意してください。解説は「// New」と書かれている箇所のみ行います。

変数の宣言

  • bool first = true; ... bool型の変数 first が true であることを宣言
  • int Counter; ... int型の変数 Counter を宣言
  • int i; ... int型の変数 i を宣言
  • int CsvCounter = 0; ... int型の変数 CsvCounter が 0 であることを宣言
  • private List CsvVariable = new List(); ... string型の List として CsvVariable を宣言

Start()

  • New_1
    • CsvファイルをUnityに読み込ませる
    • こちらの記事 とやっていることは全く同じで、詳しい解説も載っているのでここでは割愛します
  • New_2
    • 「for (i = 0; i < values.Length; i++)」の処理 ... 取得したCsvファイルの値を List の中に格納する処理
      • C# のforループの書き方については こちら を参照してください

Update()

  • New_3
    • 「if (Sd1_on.activeSelf)」の処理 ... Sd1_on がアクティブな時(弁別刺激点灯時)の処理
      • 弁別刺激が点灯したら制限時間をカウントアップ形式で作成
      • 「if (Input.GetKeyDown(KeyCode.F))」の処理 ... キーボードのFキーが押されたときの処理
        • 効果音( Operandum1SE )が鳴る
        • Counterのカウントが1増加
  • New_4
    • 「if (first)」の処理 ... 取得するList内の要素の順番についての処理
      • 「if (Counter == int.Parse(CsvVariable[CsvCounter]))」の処理 ... CounterがVRのスケジュール値に到達したときの処理
        • Ramp_off を非アクティブ化、Ramp_on をアクティブ化
          → 疑似的に強化可能ランプの点灯を表現
        • CsvCounterが1つ増加
          → 取得するList内の要素の順番を1つずらす
        • first を false にする → 取得するList内の要素の順番が2つ以上ずれないようにする
        • Invoke("ResetTimes", Ramp_Script.ReinforceableTime)
          → 強化可能ランプ点灯時から強化可能時間が経過すると、Counter を0にリセットして、first を true に戻す

5. 最後に

 「比率スケジュール(Ratio schedule)」の中の、「固定比率スケジュール(Fixed Ratio schedule)」と「変動比率スケジュール(Variable Ratio schedule)」をUnityで作る方法の解説を行いました。コードや用語等で間違っている点があれば、ご指摘いただけると幸いです。

参考URL

・NumPy, randomで様々な種類の乱数の配列を生成
https://note.nkmk.me/python-numpy-random/

・Unity で CSV ファイルを読み込む方法
https://note.com/macgyverthink/n/n83943f3bad60

・【Unity】C#の基本構文『for』
http://kimama-up.net/unity-for/

引用文献

坂上 貴之・井上 雅彦 (2018). 行動分析学──行動の科学的理解をめざして── 有斐閣

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RichTextBoxのTextを変更していないのにTextChangedイベントが発生してハマったので調べてみた (C#)

1. 結論

書式設定をするだけでもTextChangedイベントが発生する。

2. 実験

2.1 実験用コード

RichTextBoxTest.cs
using System;
using System.Drawing;
using System.Windows.Forms;

class RichTextBoxTest:Form
{
    RichTextBox rtxt;

    RichTextBoxTest()
    {
        ClientSize = new Size(400,300);

        Controls.Add( rtxt = new RichTextBox() {
            Multiline = true,
            Location = new Point(0,0),
            Size = new Size(400,270),
        });

        Button btn;
        Controls.Add( btn = new Button() {
            Location = new Point(0,270),
            Size = new Size(400,30),
            Text = "SetColor",
        });

        rtxt.TextChanged += Rtxt_TextChanged;

        btn.Click += (s,e)=>{SetColor();};
    }

    void Rtxt_TextChanged(object sender, EventArgs e)
    {
        Console.WriteLine("TextChanged called.");
    }


    void SetColor()
    {
        Console.WriteLine("setting SelectionBackColor.");
        rtxt.SelectionBackColor = Color.Yellow;

        Console.WriteLine("setting SelectionColor.");
        rtxt.SelectionColor = Color.Blue;

        Font fontForTest = new Font("MS ゴシック", 12);
        Console.WriteLine("setting SelectionFont.");
        rtxt.SelectionFont = fontForTest;

        Console.WriteLine("end of UpdateColor.");
    }

    [STAThread]
    public static void Main(string[] args)
    {
        Application.Run(new RichTextBoxTest());
    }
}

2.2. 実行結果

RichTextBoxにテキストを1文字入力して、全選択し、ボタンを押した結果。

コンソール出力
TextChanged called.
setting SelectionBackColor.
TextChanged called.
setting SelectionColor.
TextChanged called.
setting SelectionFont.
TextChanged called.
end of UpdateColor.

RichTextBoxの以下のいずれのプロパティに対しても、設定時に TextChanged イベントが発生しています。

  • SelectionBackColor
  • SelectionColor
  • SelectionFont

3. 内部仕様調査

3.1. 仕様記載

仕様記載上は、そのようには読み取れない。

Control.TextChanged Event (System.Windows.Forms) | Microsoft Docs

Occurs when the Text property value changes.

3.2. 内部コード

dotnet451\Source\ndp\fx\src\WinForms\Managed\System\WinForms

RichTextBox.csより抜粋
public Color SelectionColor {
    get {
        省略
    }
    set {
        ForceHandleCreate();
        NativeMethods.CHARFORMATA cf = GetCharFormat(true);
        cf.dwMask = RichTextBoxConstants.CFM_COLOR;
        cf.dwEffects = 0;
        cf.crTextColor = ColorTranslator.ToWin32(value);

        // set the format information
        UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_SETCHARFORMAT, RichTextBoxConstants.SCF_SELECTION, cf);
    }
}

明示的にTextChangedイベントを発火させている処理は見当たらない。
SendMessageが要因っぽい予感。

WindowsのMessageを処理してTextChangedイベントを発火しているのは、継承元のTextBoxBaseクラスのよう。

TextBoxBase.csより抜粋
private void WmReflectCommand(ref Message m) {
    if (!textBoxFlags[codeUpdateText] && !textBoxFlags[creatingHandle]) {
        if (NativeMethods.Util.HIWORD(m.WParam) == NativeMethods.EN_CHANGE && CanRaiseTextChangedEvent) {
            OnTextChanged(EventArgs.Empty);
        }
        省略
    }
}

確証が持てないが、
UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_SETCHARFORMAT, RichTextBoxConstants.SCF_SELECTION, cf);

NativeMethods.Util.HIWORD(m.WParam) == NativeMethods.EN_CHANGEを満たすメッセージを発火しているのではないか。

3.3. Window Messageを監視してみる

3.3.1. テストコード

RichTextBoxを、下記に置き換えてRichTextBoxのWindowメッセージを監視してみた。

テストコード(C#)
public class MyRichTextBox:RichTextBox
{
    protected override void WndProc(ref Message m)
    {
        long wparam = m.WParam.ToInt64();
        long lparam = m.LParam.ToInt64();

        Console.Write( "Msg:0x"    +  m.Msg.ToString("X08"));
        Console.Write(" Wparam:0x" + wparam.ToString("X08"));
        Console.Write(" Lparam:0x" + lparam.ToString("X08"));
        Console.WriteLine();

        base.WndProc(ref m);
    }
}

3.3.2. 結果

コンソール出力
setting SelectionBackColor.
Msg:0x00000444 Wparam:0x00000001 Lparam:0x00D3E460
Msg:0x00002111 Wparam:0x030007CC Lparam:0x000707CC
TextChanged called.
setting SelectionColor.
Msg:0x0000043A Wparam:0x00000001 Lparam:0x00D3E440
Msg:0x00000444 Wparam:0x00000001 Lparam:0x00D3E4B0
Msg:0x00002111 Wparam:0x030007CC Lparam:0x000707CC
TextChanged called.
setting SelectionFont.
Msg:0x00000444 Wparam:0x00000001 Lparam:0x00D3E470
Msg:0x00002111 Wparam:0x030007CC Lparam:0x000707CC
TextChanged called.
end of UpdateColor.

UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_SETCHARFORMAT, RichTextBoxConstants.SCF_SELECTION, cf);

NativeMethods.Util.HIWORD(m.WParam) == NativeMethods.EN_CHANGEを満たすメッセージを発火しているのではないか。

内部コードをみると、

  • EM_SETCHARFORMAT0x444
  • EN_CHANGE0x0300

に対して、結果として、

Msg:0x00000444 Wparam:.......... Lparam:........
Msg:.......... Wparam:0x0300.... Lparam:........

が得られているため、推測の裏どりが取れた。

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#] クラス・メソッドについて

戻り値の無いメソッドでもreturnは使える

ClassSample.cs
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Print12Hour(5);
            Print12Hour(18);
            Print12Hour(25);
        }

        static void Print12Hour(int hour)
        {
            if (hour < 0 || 24 < hour)
            {
                Console.WriteLine("正しい値ではありません");
                return;
            }
            if (hour <= 12)
            {
                Console.WriteLine("午前{0}時です", hour);
            }
            else
            {
                Console.WriteLine("午後{0}時です", hour - 12);
            }
        }
    }
}

スコープ

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var array = new int[] { 5, 8, 4, 9, -3, 6 };
            var foundIndex = -1;

            for (var i = 0; i < array.Length; i++)
            {
                if (array[i] < 0)
                {
                    foundIndex = i;  //変数iはforループの外では見えないのでfoundIndexに代入
                    break;
                }
            }
            if (foundIndex >= 0)
            {
                Console.WriteLine($"配列最初のマイナス値は{array[foundIndex]}です");
            }
            else
            {
                Console.WriteLine("配列にマイナス値はありませんでした");
            }
        }
    }
}

インスタンスプロパティ,インスタンスメソッド

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var date = new DateTime(2020, 11, 7);
            var year = date.Year;
            var month = date.Month;
            var day = date.Day;
            Console.WriteLine("{0}年{1}月{2}日", year, month, date);
        }
    }
}
ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var date = new DateTime(2020, 11, 7);
            var date1 = date.AddDays(1);    //1日後を求める
            var date2 = date.AddMonths(6);  //6か月後を求める
            Console.WriteLine(date1);
            Console.WriteLine(date2);
        }
    }
}

静的メソッド,静的プロパティ

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var isLeapYear = DateTime.IsLeapYear(2020); //IsLeapYearは静的メソッド
            if (isLeapYear)
            {
                Console.WriteLine("うるう年です");
            }
        }
    }
}
ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var today = DateTime.Today; //Todayは静的プロパティ、todayはDateTime型
            Console.WriteLine($"{today.Year}{today.Month}{today.Day}日");
        }
    }
}
ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            for (var year = 1868; year <= 2030; year++)
            {
                if (DateTime.IsLeapYear(year))  //インスタンスを生成しないでメソッドを呼び出している
                {
                    Console.WriteLine(year);
                }
            }
        }
    }
}
ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var book1 = new Book { Title = "伊豆の踊子", Auther = "川端康成" };
            book1.PrintTitle();
            var book2 = new Book { Title = "走れメロス", Auther = "太宰治" };
            book2.PrintTitle();
            var book3 = new Book { Title = "銀河鉄道の夜", Auther = "宮沢賢治" };
            book3.PrintTitle();
            Book.ClearCount();
            Console.WriteLine(Book.Count);
        }
    }

    class Book
    {
        public static int Count { get; set; }   //静的プロパティ

        public static void ClearCount() //静的メソッド
        {
            Count = 0;
        }

        public string Title { get; set; }
        public string Auther { get; set; }

        public void PrintTitle()
        {
            Console.WriteLine("書籍名: {0}", Title);
            Count++;

            Console.WriteLine(Count);
        }
    }
}

静的クラス

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var scores = new int[] { 46, 59, 48, 93, 29, 20, 70 };
            var total = ArrayUtils.Total(scores);   //静的メソッドの呼び出し
            var average = ArrayUtils.Average(scores);   //静的メソッドの呼び出し
            Console.WriteLine($"合計:{total}, 平均:{average}");
        }
    }

    static class ArrayUtils //staticメソッドで静的クラスにする
    {
        public static int Total(int[] numbers)  //配列内の数値の合計を求める
        {
            var total = 0;
            foreach (var n in numbers)
            {
                total += n;
            }
            return total;
        }

        public static double Average(int[] numbers)
        {
            var total = Total(numbers); //上記のTotalメソッドを呼び出す
            return (int)total / numbers.Length;
        }
    }
}

Stringクラス

部分文字を取り出す

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var str = "オブジェクト指向";
            var sub1 = str.Substring(0,6);
            var sub2 = str.Substring(6, 2);
            Console.WriteLine(str);
            Console.WriteLine(sub1);
            Console.WriteLine(sub2);
        }
    }
}

前後の空白文字を取り除く
Trim()

文字列の英字を大文字/小文字にする
ToUpper()
ToLower()

文字列の一部を置き換える
RePlace("staticメソッド","静的メソッド")

staticメソッドが静的メソッドの文字に置き換えられる

指定した部分文字列が存在するかどうかを調べる
Contain("調べたい文字列")

文字列を指定した文字で分割する
var items = str.Split(',')
//カンマで分割する。itemsはstringの配列(string[])になる

Mathクラス

絶対値を求める
Math.Abs(150);
Math.Abs(-320);
Math.Abs(-5,67m);
Math.Abs(-1.414);

どちらが大きい/小さいを求める
var max = Math.Max(value1, value2);

var min = Math.Max(value1, value2);

小数点以下を切り捨てる/切り上げる
var floor = Math.Floor(n);

var ceiling = Math.Ceiling(n);

小数点以下を四捨五入する
var r1 = Math.Round(6.4, MidpointRounding.AwayFromZero); //四捨五入

var r2 = Math,Round(6.4); //偶数丸め

DateTime構造体

現在の日時を取得する
var now = DateTime.Now; //現在の日時を取得する
var now = DateTime.Today; //今日の日時を取得
Console.WriteLine("{0}年, now.Year");

他はこれに変える
now.Month
now.Day
now.Hour
now.Minute
now.Second

書式を指定して日付を文字列に変換する
ToString("d");
ToString("D"); など

その他
f, F, yyyy, yy, MM, M, dd, d, HH, H, hh, h, mm, m, ss, s, ddd, tt
yearやsecondなどの頭文字だと思えば覚えやすい

Fileクラス

テキストファイルを作成する

ClassSample
using System;
using System.IO;    //Fileクラスを使用する際に必要

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var lines = new string[]
            {
                "あああああああああああ",
                "いいいいいいいいいいいい",
                "うううううううううううううう",
                "ええええええええええええええええ"
            };
            File.WriteAllLines(@"C:\\temp\\ああああ.txt", lines);
        }
    }
}

これを実行するとtempフォルダにテキストファイルが作成される(Cドライブ直下にtempフォルダを作っておくこと)

スクリーンショット 2021-02-26 021559.png

読み込むときは下記コードを書く

ClassSample
using System;
using System.IO;    //Fileクラスを使用する際に必要

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var lines = File.ReadAllLines(@"C:\temp\ああああ.txt");
            foreach (var line in lines)
            {
                Console.WriteLine(line);
            }
        }
    }
}

配列を返すメソッド

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var nums = ArrayUtils.GetArray(5);
            var total = 0;
            foreach (var x in nums)
            {
                total += x;
            }
            Console.WriteLine($"合計: {total}");
        }

        static class ArrayUtils
        {
            public static int[] GetArray(int count)
            {
                var array = new int[count];
                for (var i = 0; i < count; i++)
                {
                    var line = Console.ReadLine();
                    array[i] = int.Parse(line);
                }
                return array;
            }
        }
    }
}

オブジェクトを返すメソッド

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var book1 = MakeBookObject();
            var book2 = MakeBookObject();

            book1.Print();
            book2.Print();

            static Book MakeBookObject()    //Book型を返すメソッド
            {
                Console.Write("書籍名⇒");
                var title = Console.ReadLine();
                Console.Write("著者名⇒");
                var auther = Console.ReadLine();
                Console.Write("ページ数⇒");
                var pages = Console.ReadLine();
                var book = new Book
                {
                    Title = title,
                    Auther = auther,
                    Pages = int.Parse(pages),
                    Rating = 3
                };
                return book;    //Bookオブジェクトを返す
            }
        }

        class Book
        {
            public string Title { get; set; }
            public string Auther { get; set; }
            public int Pages { get; set; }
            public int Rating { get; set; }

            public void Print()
            {
                Console.WriteLine($"★{Title}");
                Console.WriteLine($"{Auther} {Pages}ページ {Rating}");
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#] 自分復習用①

戻り値の無いメソッドでもreturnは使える

ClassSample.cs
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Print12Hour(5);
            Print12Hour(18);
            Print12Hour(25);
        }

        static void Print12Hour(int hour)
        {
            if (hour < 0 || 24 < hour)
            {
                Console.WriteLine("正しい値ではありません");
                return;
            }
            if (hour <= 12)
            {
                Console.WriteLine("午前{0}時です", hour);
            }
            else
            {
                Console.WriteLine("午後{0}時です", hour - 12);
            }
        }
    }
}

スコープ

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var array = new int[] { 5, 8, 4, 9, -3, 6 };
            var foundIndex = -1;

            for (var i = 0; i < array.Length; i++)
            {
                if (array[i] < 0)
                {
                    foundIndex = i;  //変数iはforループの外では見えないのでfoundIndexに代入
                    break;
                }
            }
            if (foundIndex >= 0)
            {
                Console.WriteLine($"配列最初のマイナス値は{array[foundIndex]}です");
            }
            else
            {
                Console.WriteLine("配列にマイナス値はありませんでした");
            }
        }
    }
}

インスタンスプロパティ,インスタンスメソッド

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var date = new DateTime(2020, 11, 7);
            var year = date.Year;
            var month = date.Month;
            var day = date.Day;
            Console.WriteLine("{0}年{1}月{2}日", year, month, date);
        }
    }
}
ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var date = new DateTime(2020, 11, 7);
            var date1 = date.AddDays(1);    //1日後を求める
            var date2 = date.AddMonths(6);  //6か月後を求める
            Console.WriteLine(date1);
            Console.WriteLine(date2);
        }
    }
}

静的メソッド,静的プロパティ

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var isLeapYear = DateTime.IsLeapYear(2020); //IsLeapYearは静的メソッド
            if (isLeapYear)
            {
                Console.WriteLine("うるう年です");
            }
        }
    }
}
ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var today = DateTime.Today; //Todayは静的プロパティ、todayはDateTime型
            Console.WriteLine($"{today.Year}{today.Month}{today.Day}日");
        }
    }
}
ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            for (var year = 1868; year <= 2030; year++)
            {
                if (DateTime.IsLeapYear(year))  //インスタンスを生成しないでメソッドを呼び出している
                {
                    Console.WriteLine(year);
                }
            }
        }
    }
}
ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var book1 = new Book { Title = "伊豆の踊子", Auther = "川端康成" };
            book1.PrintTitle();
            var book2 = new Book { Title = "走れメロス", Auther = "太宰治" };
            book2.PrintTitle();
            var book3 = new Book { Title = "銀河鉄道の夜", Auther = "宮沢賢治" };
            book3.PrintTitle();
            Book.ClearCount();
            Console.WriteLine(Book.Count);
        }
    }

    class Book
    {
        public static int Count { get; set; }   //静的プロパティ

        public static void ClearCount() //静的メソッド
        {
            Count = 0;
        }

        public string Title { get; set; }
        public string Auther { get; set; }

        public void PrintTitle()
        {
            Console.WriteLine("書籍名: {0}", Title);
            Count++;

            Console.WriteLine(Count);
        }
    }
}

静的クラス

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var scores = new int[] { 46, 59, 48, 93, 29, 20, 70 };
            var total = ArrayUtils.Total(scores);   //静的メソッドの呼び出し
            var average = ArrayUtils.Average(scores);   //静的メソッドの呼び出し
            Console.WriteLine($"合計:{total}, 平均:{average}");
        }
    }

    static class ArrayUtils //staticメソッドで静的クラスにする
    {
        public static int Total(int[] numbers)  //配列内の数値の合計を求める
        {
            var total = 0;
            foreach (var n in numbers)
            {
                total += n;
            }
            return total;
        }

        public static double Average(int[] numbers)
        {
            var total = Total(numbers); //上記のTotalメソッドを呼び出す
            return (int)total / numbers.Length;
        }
    }
}

Stringクラス

部分文字を取り出す

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var str = "オブジェクト指向";
            var sub1 = str.Substring(0,6);
            var sub2 = str.Substring(6, 2);
            Console.WriteLine(str);
            Console.WriteLine(sub1);
            Console.WriteLine(sub2);
        }
    }
}

前後の空白文字を取り除く
Trim()

文字列の英字を大文字/小文字にする
ToUpper()
ToLower()

文字列の一部を置き換える
RePlace("staticメソッド","静的メソッド")

staticメソッドが静的メソッドの文字に置き換えられる

指定した部分文字列が存在するかどうかを調べる
Contain("調べたい文字列")

文字列を指定した文字で分割する
var items = str.Split(',')
//カンマで分割する。itemsはstringの配列(string[])になる

Mathクラス

絶対値を求める
Math.Abs(150);
Math.Abs(-320);
Math.Abs(-5,67m);
Math.Abs(-1.414);

どちらが大きい/小さいを求める
var max = Math.Max(value1, value2);

var min = Math.Max(value1, value2);

小数点以下を切り捨てる/切り上げる
var floor = Math.Floor(n);

var ceiling = Math.Ceiling(n);

小数点以下を四捨五入する
var r1 = Math.Round(6.4, MidpointRounding.AwayFromZero); //四捨五入

var r2 = Math,Round(6.4); //偶数丸め

DateTime構造体

現在の日時を取得する
var now = DateTime.Now; //現在の日時を取得する
var now = DateTime.Today; //今日の日時を取得
Console.WriteLine("{0}年, now.Year");

他はこれに変える
now.Month
now.Day
now.Hour
now.Minute
now.Second

書式を指定して日付を文字列に変換する
ToString("d");
ToString("D"); など

その他
f, F, yyyy, yy, MM, M, dd, d, HH, H, hh, h, mm, m, ss, s, ddd, tt
yearやsecondなどの頭文字だと思えば覚えやすい

Fileクラス

テキストファイルを作成する

ClassSample
using System;
using System.IO;    //Fileクラスを使用する際に必要

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var lines = new string[]
            {
                "あああああああああああ",
                "いいいいいいいいいいいい",
                "うううううううううううううう",
                "ええええええええええええええええ"
            };
            File.WriteAllLines(@"C:\\temp\\ああああ.txt", lines);
        }
    }
}

これを実行するとtempフォルダにテキストファイルが作成される(Cドライブ直下にtempフォルダを作っておくこと)

スクリーンショット 2021-02-26 021559.png

読み込むときは下記コードを書く

ClassSample
using System;
using System.IO;    //Fileクラスを使用する際に必要

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var lines = File.ReadAllLines(@"C:\temp\ああああ.txt");
            foreach (var line in lines)
            {
                Console.WriteLine(line);
            }
        }
    }
}

配列を返すメソッド

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var nums = ArrayUtils.GetArray(5);
            var total = 0;
            foreach (var x in nums)
            {
                total += x;
            }
            Console.WriteLine($"合計: {total}");
        }

        static class ArrayUtils
        {
            public static int[] GetArray(int count)
            {
                var array = new int[count];
                for (var i = 0; i < count; i++)
                {
                    var line = Console.ReadLine();
                    array[i] = int.Parse(line);
                }
                return array;
            }
        }
    }
}

オブジェクトを返すメソッド

ClassSample
using System;

namespace ClassSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var book1 = MakeBookObject();
            var book2 = MakeBookObject();

            book1.Print();
            book2.Print();

            static Book MakeBookObject()    //Book型を返すメソッド
            {
                Console.Write("書籍名⇒");
                var title = Console.ReadLine();
                Console.Write("著者名⇒");
                var auther = Console.ReadLine();
                Console.Write("ページ数⇒");
                var pages = Console.ReadLine();
                var book = new Book
                {
                    Title = title,
                    Auther = auther,
                    Pages = int.Parse(pages),
                    Rating = 3
                };
                return book;    //Bookオブジェクトを返す
            }
        }

        class Book
        {
            public string Title { get; set; }
            public string Auther { get; set; }
            public int Pages { get; set; }
            public int Rating { get; set; }

            public void Print()
            {
                Console.WriteLine($"★{Title}");
                Console.WriteLine($"{Auther} {Pages}ページ {Rating}");
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む