【C#】5ちゃんねるビューアーを作ろう(WPF) その2
今回の記事は前回の記事の続きとして書いていきます。
今回は、WPFのListBox(ListViewではない点に注意)について書いていきたいと思います。
何故ListViewではないかというと、WindowsフォームアプリケーションのListViewにはListViewItem.Tagを使う事に意味があったからです。
WPFの場合任意のクラスをバインディングできるため、ListBoxで必要十分であります。
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}" SelectedItemChanged="TreeView_SelectedItemChanged"> <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"/> <ListBox Grid.Column="2" ItemsSource="{Binding Threads}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Text}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> <GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch"/> </Grid> </Window>
- ListBoxはTreeView同様ItemsSourceにバインディングしてやり、ItemTemplateにDataTemplateを指定してやるだけでOK
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; } } } } private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { if (((TreeSource)e.NewValue).URL == null) return; string html = ""; Regex regex = new Regex("(?:<a href=\")(.*)(?:\">)(.*)(?:</a>)"); //まずHTMLの取得 try { HttpWebRequest req = WebRequest.CreateHttp(((TreeSource)e.NewValue).URL); using (var res = req.GetResponse()) { using (var r = res.GetResponseStream()) { using (var sr = new StreamReader(r, Encoding.GetEncoding(932))) { html = sr.ReadToEnd(); } } } } catch (Exception ex) { MessageBox.Show("HTMLの取得に失敗しました。", "エラー", MessageBoxButton.OK, MessageBoxImage.Error); } //正規表現でhref属性値とスレッドタイトルを取得する vm.Threads.Clear(); try { var st = html.LastIndexOf("<small id=\"trad\">"); var ed = html.IndexOf("</small>", st); var ms = regex.Matches(html); foreach (Match m in ms) { if (m.Index < st || m.Index > ed) continue; vm.Threads.Add(new Thread(m.Groups[2].Value, m.Groups[1].Value)); } } catch (Exception ex) { MessageBox.Show("HTMLの読取に失敗しました。", "エラー", MessageBoxButton.OK, MessageBoxImage.Error); } } } 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"); } } ObservableCollection<Thread> _Threads = new ObservableCollection<Thread>(); public ObservableCollection<Thread> Threads { get { return _Threads; } set { _Threads = value; OnPropertyChanged("Threads"); } } } 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>(); } } public class Thread { public string Text { get; set; } public string URL { get; set; } public Thread(string text,string url) { Text = text; URL = url; } } }
- TreeVIewの選択箇所が変わった時のイベントはTreeView_SelectedItemChanged
- WPFのMessageBoxはWindowsフォームアプリケーションのとは少し違うので注意
- ListBoxにバインディングしてやるThreadsもObservableCollectionで作ってあげる必要がある