HeyCHのブログ

慢性疲労のへいちゃんです

【C#】5ちゃんねるビューアーを作ろう(WPF) その1

今回から数回に分けて、5ちゃんねるビューアーをWPF化してみようと思います。
今回はTreeView部分のみ作っていこうと思います。
また、以下の記事を前提に書いていきます。
heych.hatenablog.com
heych.hatenablog.com

<Window x:Class="_5chViewerWPF.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:_5chViewerWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="600">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="200"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <TreeView Grid.Column="0" ItemsSource="{Binding Menus}">
                <TreeView.Resources>
                    <Style TargetType="TreeViewItem">
                        <Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
                    </Style>
                </TreeView.Resources>
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate DataType="{x:Type local:TreeSource}" ItemsSource="{Binding Children}">
                        <TextBlock Text="{Binding Text}"/>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
            <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch"/>
        </Grid>
        <GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch"/>
    </Grid>
</Window>
  • GridSplitter を使う事で、Gridの幅や高さを動的に変えることができる。
  • TreeViewItemのIsExpandedプロパティをIsExpandedにバインドすることができる。
  • TreeViewのItemTemplateにおいてHierarchicalDataTemplate を用いることで、ツリー構造のバインディングを実現できる。
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
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 _5chViewerWPF {
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window {
        ViewModel vm = new ViewModel();
        public MainWindow() {
            InitializeComponent();
            this.DataContext = vm;

            string html = "";
            //まずHTMLの取得
            HttpWebRequest req = WebRequest.CreateHttp("https://www2.5ch.net/5ch.html");
            using (var res = req.GetResponse()) {
                using (var r = res.GetResponseStream()) {
                    using (var sr = new StreamReader(r, Encoding.GetEncoding(932))) {
                        html = sr.ReadToEnd();
                    }
                }
            }
            //Treeの作成
            bool stTag = false;
            bool edTag = false;
            bool isAttr = false;
            string currentTag = null;
            string currentAttr = null;
            string tmp = null;
            Regex regex = new Regex("(?:href=\")(.*)(?:\")");
            for (int i = 0; i < html.Length; i++) {
                if (html[i] == '<') {
                    if (html[i + 1] == '/') {
                        if (tmp != null) {
                            if (currentTag == "B") {
                                TreeSource tn = new TreeSource(tmp);
                                vm.Menus.Add(tn);
                            } else if (currentTag == "a" && vm.Menus.Count > 0) {
                                string href = "https:" + regex.Match(currentAttr).Groups[1].Value + "subback.html";
                                TreeSource tn = new TreeSource(tmp);
                                tn.URL = href;
                                vm.Menus[vm.Menus.Count - 1].Children.Add(tn);
                            }
                        }
                        edTag = true;
                    } else {
                        stTag = true;
                        tmp = null;
                        currentTag = null;
                        currentAttr = null;
                    }
                } else if (html[i] == '>') {
                    if (edTag) currentTag = null;
                    stTag = false;
                    edTag = false;
                    isAttr = false;
                } else if (html[i] == ' ' && stTag) {
                    isAttr = true;
                } else {
                    if (isAttr) currentAttr += html[i];
                    else if (stTag) currentTag += html[i];
                    else if (!edTag && (currentTag == "B" || currentTag == "a")) {
                        tmp += html[i];
                        if (tmp == "他のサイト") break;
                    }
                }
            }
        }
    }
    public class ViewModel : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName) {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        ObservableCollection<TreeSource> _Menus = new ObservableCollection<TreeSource>();
        public ObservableCollection<TreeSource> Menus {
            get { return _Menus; }
            set {
                _Menus = value;
                OnPropertyChanged("Menus");
            }
        }
    }
    public class TreeSource {
        public TreeSource Parent { get; set; }
        public ObservableCollection<TreeSource> Children { get; set; }
        public bool IsExpanded { get; set; }
        public string Text { get; set; }
        public string URL { get; set; }
        public TreeSource(string text) {
            Text = text;
            Children = new ObservableCollection<TreeSource>();
        }
    }
}

f:id:HeyCH:20200426002222p:plain