回答編集履歴

4

無駄x:Name

2023/05/24 15:52

投稿

TN8001
TN8001

スコア9321

test CHANGED
@@ -99,7 +99,7 @@
99
99
  </Style>
100
100
  </Window.Resources>
101
101
 
102
- <Grid x:Name="rootGrid">
102
+ <Grid>
103
103
  <Grid.ColumnDefinitions>
104
104
  <ColumnDefinition Width="2*" />
105
105
  <ColumnDefinition />

3

(7*6週分=42個)

2023/05/24 15:48

投稿

TN8001
TN8001

スコア9321

test CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  > 別月に移動すると同じセル位置に文字が表示される
7
7
 
8
- `CalendarDayButton`は1か月分しか作らずに、すべての月で使いまわしているせいですね。
8
+ `CalendarDayButton`は1か月分(7*6週分=42個)しか作らずに、すべての月で使いまわしているせいですね。
9
9
 
10
10
  > WPFのカレンダーコントロールのコメント欄を追加したい
11
11
 

2

わかりやすさ重視でコードビハインドに変更

2023/05/24 15:47

投稿

TN8001
TN8001

スコア9321

test CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  参考サイトではすべてのコメントを`ObservableCollection`で保持し、`MultiValueConverter`で**その日**のコメントを表示するのが肝になっています。
13
13
 
14
- `TextBox.Text`を`TwoWay`バインドしようとすと非常にかったるいので、コードビハインドました^^;
14
+ ですが`ConvertBack`で追加や変更反映できわけでもないので、わかりやすさ重視ですべてコードビハインド更しました^^(MVVM教の方はビヘイビアにでもしてください)
15
15
 
16
16
  あとなぜか`TextBox`にキーボードフォーカスが入りません(`Calendar`が選択周りで何かやってそう)でしたので、クソい手を使っていますw
17
17
 
@@ -22,20 +22,27 @@
22
22
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
23
23
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
24
24
  xmlns:gl="clr-namespace:System.Globalization;assembly=mscorlib"
25
- xmlns:local="clr-namespace:Qa6deqmgkhn71do"
26
25
  SizeToContent="WidthAndHeight">
27
26
  <Window.Resources>
28
- <local:CalendarCommentConverter x:Key="CalendarCommentConverter" />
29
-
30
27
  <Style x:Key="CalendarDayButtonStyle1" TargetType="{x:Type CalendarDayButton}">
28
+
29
+ <!-- デフォルトからの変更点 追加 -->
31
30
  <EventSetter Event="PreviewMouseUp" Handler="CalendarDayButton_PreviewMouseUp" />
31
+
32
32
  <Setter Property="MinWidth" Value="5" />
33
33
  <Setter Property="MinHeight" Value="5" />
34
+
35
+ <!-- デフォルトからの変更点 削除 -->
36
+ <!--<Setter Property="FontSize" Value="10" />-->
37
+
34
38
  <Setter Property="HorizontalContentAlignment" Value="Center" />
35
39
  <Setter Property="VerticalContentAlignment" Value="Center" />
36
40
  <Setter Property="Template">
37
41
  <Setter.Value>
38
42
  <ControlTemplate TargetType="{x:Type CalendarDayButton}">
43
+
44
+ <!-- デフォルトからの変更点 Width・Height追加 -->
45
+ <!--<Grid>-->
39
46
  <Grid Width="80" Height="80">
40
47
  <Rectangle x:Name="TodayBackground" Fill="#FFAAAAAA" Opacity="0" RadiusX="1" RadiusY="1" />
41
48
  <Rectangle x:Name="SelectedBackground" Fill="#FFBADDE9" Opacity="0" RadiusX="1" RadiusY="1" />
@@ -44,6 +51,14 @@
44
51
  BorderBrush="{TemplateBinding BorderBrush}"
45
52
  BorderThickness="{TemplateBinding BorderThickness}" />
46
53
  <Rectangle x:Name="HighlightBackground" Fill="#FFBADDE9" Opacity="0" RadiusX="1" RadiusY="1" />
54
+
55
+ <!-- デフォルトからの変更点 VerticalAlignment="Top" -->
56
+ <!--<ContentPresenter
57
+ x:Name="NormalText"
58
+ Margin="5,1,5,1"
59
+ HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
60
+ VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
61
+ TextElement.Foreground="#FF333333" />-->
47
62
  <ContentPresenter
48
63
  x:Name="NormalText"
49
64
  Margin="5,1,5,1"
@@ -51,19 +66,17 @@
51
66
  VerticalAlignment="Top"
52
67
  TextElement.Foreground="#FF333333" />
53
68
 
69
+ <!-- デフォルトからの変更点 追加 -->
54
70
  <TextBox
55
71
  x:Name="CalenderTimeTextBox"
56
72
  Margin="3"
57
73
  VerticalAlignment="Bottom"
58
74
  BorderThickness="0"
75
+ DataContextChanged="CalenderTimeTextBox_DataContextChanged"
76
+ Loaded="CalenderTimeTextBox_Loaded"
59
- TextChanged="CalenderTimeTextBox_TextChanged">
77
+ TextChanged="CalenderTimeTextBox_TextChanged" />
60
- <TextBox.Text>
78
+
61
- <MultiBinding Converter="{StaticResource CalendarCommentConverter}" Mode="OneWay">
62
- <Binding Path="Date" />
63
- <Binding ElementName="rootGrid" Path="DataContext.Comments" />
64
- </MultiBinding>
65
- </TextBox.Text>
66
- </TextBox>
79
+ <!-- 以下変更なし -->
67
80
 
68
81
  <Path
69
82
  x:Name="Blackout"
@@ -92,7 +105,10 @@
92
105
  <ColumnDefinition />
93
106
  </Grid.ColumnDefinitions>
94
107
 
108
+ <Calendar
109
+ x:Name="calendar"
110
+ Background="White"
95
- <Calendar Background="White" CalendarDayButtonStyle="{StaticResource CalendarDayButtonStyle1}" />
111
+ CalendarDayButtonStyle="{StaticResource CalendarDayButtonStyle1}" />
96
112
 
97
113
  <DataGrid
98
114
  Grid.Column="1"
@@ -110,14 +126,13 @@
110
126
 
111
127
  ```cs
112
128
  using System;
129
+ using System.Collections.Generic;
113
130
  using System.Collections.ObjectModel;
114
- using System.Globalization;
115
131
  using System.Linq;
116
132
  using System.Threading.Tasks;
117
133
  using System.Windows;
118
134
  using System.Windows.Controls;
119
135
  using System.Windows.Controls.Primitives;
120
- using System.Windows.Data;
121
136
  using System.Windows.Input;
122
137
  using System.Windows.Media;
123
138
  using CommunityToolkit.Mvvm.ComponentModel;
@@ -125,104 +140,130 @@
125
140
 
126
141
  namespace Qa6deqmgkhn71do
127
142
  {
128
- public class ViewModel
129
- {
130
- public ObservableCollection<Comment> Comments { get; }
131
-
132
- public ViewModel()
133
- {
134
- Comments = new ObservableCollection<Comment>
135
- {
136
- new Comment("first", DateTime.Now),
137
- new Comment("second", DateTime.Now.AddDays(1)),
138
- new Comment("third", DateTime.Now.AddDays(7)),
139
- new Comment("forth", DateTime.Now.AddMonths(1)),
140
- new Comment("fifth", DateTime.Now.AddMonths(1).AddDays(1)),
141
- new Comment("sixth", DateTime.Now.AddMonths(1).AddDays(7)),
142
- };
143
- }
144
- }
145
-
146
- public class Comment : ObservableObject
147
- {
148
- public string Value { get => _Value; set => SetProperty(ref _Value, value); }
149
- private string _Value;
150
-
151
- public DateTime Date { get; }
152
-
153
- public Comment(string value, DateTime date) => (Value, Date) = (value, date);
154
- }
155
-
156
- public class CalendarCommentConverter : IMultiValueConverter
157
- {
158
- public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
159
- {
160
- if (values[0] is DateTime target && values[1] is ObservableCollection<Comment> comments)
161
- return comments.FirstOrDefault(x => x.Date.Date == target.Date)?.Value;
162
- return DependencyProperty.UnsetValue;
163
- }
164
- public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
165
- }
166
-
167
- public partial class MainWindow : Window
168
- {
169
- private readonly ViewModel vm = new ViewModel();
170
-
171
- public MainWindow()
172
- {
173
- InitializeComponent();
174
- DataContext = vm;
175
- }
176
-
177
- private void CalenderTimeTextBox_TextChanged(object sender, TextChangedEventArgs e)
178
- {
179
- if (sender is TextBox textBox && textBox.DataContext is DateTime target)
180
- {
181
- // 同日にコメントは1件の想定
182
- var comment = vm.Comments.FirstOrDefault(x => x.Date.Date == target.Date);
183
-
184
- if (comment != null) // すでにコメントがあれば...
185
- {
186
- if (!string.IsNullOrEmpty(textBox.Text))
187
- comment.Value = textBox.Text; // Valueを更新
188
- else
189
- vm.Comments.Remove(comment); // 空コメントはまるごと削除
190
- }
191
- else // コメントがまだなければ...
192
- {
193
- if (!string.IsNullOrEmpty(textBox.Text))
194
- vm.Comments.Add(new Comment(textBox.Text, target)); // 追加
195
- }
196
- }
197
- }
198
-
199
- private async void CalendarDayButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
200
- {
201
- if (sender is CalendarDayButton button)
202
- {
203
- var textBox = FindVisualChild<TextBox>(button);
204
-
205
- await Task.Delay(1); // メソッドを抜けてからフォーカスw
206
- textBox?.Focus();
207
- }
208
- }
209
-
210
-
211
- private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
212
- {
213
- for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
214
- {
215
- var child = VisualTreeHelper.GetChild(parent, i);
216
- if (child is T t) return t;
217
- var childOfChild = FindVisualChild<T>(child);
218
- if (childOfChild != null) return childOfChild;
219
- }
220
- return null;
221
- }
222
- }
143
+ public class ViewModel
144
+ {
145
+ public ObservableCollection<Comment> Comments { get; }
146
+
147
+ public ViewModel()
148
+ {
149
+ Comments = new ObservableCollection<Comment>
150
+ {
151
+ new Comment("first", DateTime.Now),
152
+ new Comment("second", DateTime.Now.AddDays(1)),
153
+ new Comment("third", DateTime.Now.AddDays(7)),
154
+ new Comment("forth", DateTime.Now.AddMonths(1)),
155
+ new Comment("fifth", DateTime.Now.AddMonths(1).AddDays(1)),
156
+ new Comment("sixth", DateTime.Now.AddMonths(1).AddDays(7)),
157
+ };
158
+ }
159
+ }
160
+
161
+ public class Comment : ObservableObject
162
+ {
163
+ public string Value { get => _Value; set => SetProperty(ref _Value, value); }
164
+ private string _Value;
165
+
166
+ public DateTime Date { get; } // 日時のみ(常に0:00:00)
167
+
168
+ public Comment(string value, DateTime date) => (Value, Date) = (value, date.Date);
169
+ }
170
+
171
+ public partial class MainWindow : Window
172
+ {
173
+ private readonly ViewModel vm = new ViewModel();
174
+
175
+ public MainWindow()
176
+ {
177
+ InitializeComponent();
178
+ DataContext = vm;
179
+ }
180
+
181
+ // TextBoxが読み込まれたとき(初回1回だけ)CalendarDayButton分(7*6週分=42個)呼ばれる
182
+ private void CalenderTimeTextBox_Loaded(object sender, RoutedEventArgs e)
183
+ {
184
+ if (sender is TextBox textBox && textBox.DataContext is DateTime date)
185
+ {
186
+ // TextBox(CalendarDayButton)のDataContextはDateTime(例えば2023/05/21 0:00:00)
187
+ // CommentsのDateが一致したもの(同日にコメントは1件の想定)があれば
188
+ // Value(コメント文)をTextにセット
189
+ textBox.Text = vm.Comments?.FirstOrDefault(x => x.Date == date)?.Value;
190
+ }
191
+ }
192
+
193
+ // DataContextが変わったとき(=月が替わったとき)都度CalendarDayButton分呼ばれる
194
+ private void CalenderTimeTextBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
195
+ // やることは↑と同じなので雑に呼び出しw
196
+ => CalenderTimeTextBox_Loaded(sender, default);
197
+
198
+ // ユーザーが何か文字入力(文字削除)したとき
199
+ private void CalenderTimeTextBox_TextChanged(object sender, TextChangedEventArgs e)
200
+ {
201
+ if (sender is TextBox textBox && textBox.DataContext is DateTime date)
202
+ {
203
+ // 同日にコメントは1件の想定
204
+ var comment = vm.Comments.FirstOrDefault(x => x.Date.Date == date);
205
+
206
+ if (comment != null) // すでにコメントがれば...
207
+ {
208
+ if (!string.IsNullOrEmpty(textBox.Text))
209
+ comment.Value = textBox.Text; // Valueを更新
210
+ else
211
+ vm.Comments.Remove(comment); // 空コメントはまるごと削除
212
+ }
213
+ else // コメントがまだなければ...
214
+ {
215
+ if (!string.IsNullOrEmpty(textBox.Text))
216
+ vm.Comments.Add(new Comment(textBox.Text, date)); // 追加
217
+ }
218
+ }
219
+ }
220
+
221
+ private async void CalendarDayButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
222
+ {
223
+ // キーボードフォーカスがうまく入らないのと
224
+ // カレンダーの[前の月|次の月]の日を選択すると月が替わるため
225
+ // クリック後ちょっと時間をおいてから選択日のTextBoxをフォーカス
226
+ await Task.Delay(10);
227
+ calendar.Descendants<CalendarDayButton>()
228
+ .FirstOrDefault(x => x.IsSelected)
229
+ ?.Descendants<TextBox>()
230
+ ?.FirstOrDefault()
231
+ ?.Focus();
232
+ }
233
+ }
234
+
235
+
236
+ // [VisualTreeの子孫要素を取得する - xin9le.net](https://blog.xin9le.net/entry/2013/10/29/222336)
237
+ public static class DependencyObjectExtensions
238
+ {
239
+ public static IEnumerable<DependencyObject> Children(this DependencyObject obj)
240
+ {
241
+ if (obj == null) throw new ArgumentNullException(nameof(obj));
242
+
243
+ var count = VisualTreeHelper.GetChildrenCount(obj);
244
+ if (count == 0) yield break;
245
+ for (var i = 0; i < count; i++)
246
+ {
247
+ var child = VisualTreeHelper.GetChild(obj, i);
248
+ if (child != null) yield return child;
249
+ }
250
+ }
251
+ public static IEnumerable<DependencyObject> Descendants(this DependencyObject obj)
252
+ {
253
+ if (obj == null) throw new ArgumentNullException(nameof(obj));
254
+
255
+ foreach (var child in obj.Children())
256
+ {
257
+ yield return child;
258
+ foreach (var grandChild in child.Descendants()) yield return grandChild;
259
+ }
260
+ }
261
+ public static IEnumerable<T> Descendants<T>(this DependencyObject obj) where T : DependencyObject => obj.Descendants().OfType<T>();
262
+ }
223
263
  }
224
264
  ```
225
265
  [NuGet Gallery | CommunityToolkit.Mvvm 8.2.0](https://www.nuget.org/packages/CommunityToolkit.Mvvm/8.2.0)
226
266
 
267
+ ---
227
268
 
228
269
  ![アプリ画像](https://ddjkaamml8q8x.cloudfront.net/questions/2023-05-21/1c4e2692-d416-4651-ba9f-a004797e9d42.png)

1

2023/05/21 16:15

投稿

TN8001
TN8001

スコア9321

test CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  あとなぜか`TextBox`にキーボードフォーカスが入りません(`Calendar`が選択周りで何かやってそう)でしたので、クソい手を使っていますw
17
17
 
18
- 参考サイトは同日に複数コメント対応ですが、(回答コードは)`TextBox`がひとつだけなので1件のみです。
18
+ 参考サイトは同日に複数コメント対応ですが、(回答コードは)`TextBox`がひとつだけなので1件のみです。
19
19
  ```xml
20
20
  <Window
21
21
  x:Class="Qa6deqmgkhn71do.MainWindow"