Skip to content

Commit 3a438db

Browse files
seto77claude
andcommitted
Extract Macro class and encapsulate FormMain access behind public API
- Split Macro class out of FormMain.cs into a dedicated IPAnalyzer/Macro.cs file (~850 lines moved out, FormMain.cs reduced by ~966 lines). - Introduce a dedicated "Macro 公開 API" region in FormMain.cs that exposes intent-named bool/double properties and methods instead of letting Macro touch private UI controls directly. 17 members total (e.g. AzimuthalDivisionAnalysisEnabled, IntensityDisplayMax/Min, ResizeCanvas, OpenImageDialog, IsNegativeGradient, CanvasZoom, SetCanvasCenter). - Promote saveImageAsTiff / saveImageAsPng from private to public so Macro can call them through the new facade without a case-only rename. - Rewrite all direct p.main.<control> accesses in Macro.cs to go through the new FormMain API, so no UI control leaks out of FormMain anymore. - Migrate Macro.cs help strings to the [Help] attribute pattern used in PDIndexer: attach [Help(...)] to every public property and method in 9 subclasses and generate help text via HelpAttribute.GenerateHelpText in the root Macro constructor. Fix a handful of legacy typos in the original p.help.Add strings along the way. - Version bump to 3.977 with English history entry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 04d483e commit 3a438db

10 files changed

Lines changed: 2367 additions & 2326 deletions

File tree

Crystallography.Controls/Crystallography.Controls.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<OutputType>Library</OutputType>
55
<TargetFramework>net10.0-windows</TargetFramework>
66
<UseWindowsForms>true</UseWindowsForms>
7-
<AssemblyVersion>2026.4.13.0656</AssemblyVersion>
8-
<FileVersion>2026.4.13.0656</FileVersion>
7+
<AssemblyVersion>2026.4.14.0153</AssemblyVersion>
8+
<FileVersion>2026.4.14.0153</FileVersion>
99
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
1010
<ApplicationUseCompatibleTextRendering>true</ApplicationUseCompatibleTextRendering>
1111
<ApplicationVisualStyles>true</ApplicationVisualStyles>

Crystallography.Controls/Macro/FormMacro.cs

Lines changed: 359 additions & 390 deletions
Large diffs are not rendered by default.
Lines changed: 105 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,185 +1,149 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Linq.Expressions;
54
using System.Reflection;
5+
using System.Text.Json;
66
using System.Windows.Forms;
77

88
namespace Crystallography.Controls;
99

10-
[Serializable()]
11-
public class MacroTriger(string target, bool debug, object[] obj, string macroName = "")
10+
// 注: クラス名 "MacroTriger" は typo ("Trigger") だが、IPAnalyzer 等から
11+
// `new MacroTriger("PDI", ...)` で参照されているため改名不可。
12+
//
13+
// 260414Cl 全面改修:
14+
// 旧版は [Serializable] + Clipboard.SetDataObject(triger) で送信していたが、
15+
// .NET 9 以降 WinForms Clipboard の BinaryFormatter ベース経路が廃止されサイレント
16+
// 失敗していた (csproj の EnableUnsafeBinaryFormatterSerialization は .NET 9 以降 no-op)。
17+
//
18+
// 対策: DataObject を継承し、コンストラクタ内で自身を JSON シリアライズして byte[] を
19+
// format=typeof(MacroTriger).FullName で SetData する。これにより呼び出し側
20+
// (IPAnalyzer など) は従来通り `Clipboard.SetDataObject(new MacroTriger(...))` だけで
21+
// 済み、IPAnalyzer 本体のコードを 1 行も触らずに済む。
22+
//
23+
// 受信側 (PDIndexer) は GetData(typeof(MacroTriger)) で byte[] を受け取って
24+
// MacroTriger.Deserialize(byte[]) で復元する (旧版は MacroTriger インスタンスを
25+
// そのまま受け取っていたが、現在は byte[] 経由になる)。
26+
public class MacroTriger : DataObject
1227
{
13-
public bool Debug { set; get; } = debug;
14-
public string Target { set; get; } = target;
15-
public string MacroName = macroName;
16-
public object[] Obj = obj;
28+
public string Target { get; set; }
29+
public bool Debug { get; set; }
30+
public string MacroName { get; set; }
31+
public object[] Obj { get; set; }
32+
33+
// 260414Cl JSON デシリアライズ用 parameterless ctor
34+
public MacroTriger() { }
35+
36+
public MacroTriger(string target, bool debug, object[] obj, string macroName = "")
37+
{
38+
Target = target;
39+
Debug = debug;
40+
Obj = obj;
41+
MacroName = macroName;
42+
43+
// 自身を JSON 化して byte[] として登録。
44+
// クリップボード経由の cross-process 転送で受信側はこの byte[] を受け取る。
45+
SetData(typeof(MacroTriger), Serialize());
46+
}
47+
48+
private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = false };
49+
50+
/// <summary>JSON シリアライズ。クリップボード SetData の中身として使う。</summary>
51+
public byte[] Serialize() => JsonSerializer.SerializeToUtf8Bytes(this, JsonOpts);
52+
53+
/// <summary>
54+
/// JSON byte[] から <see cref="MacroTriger"/> を復元する。
55+
/// JSON で復元すると <see cref="Obj"/> 配列要素は <see cref="JsonElement"/> になるため
56+
/// プリミティブ型へ戻す。
57+
/// </summary>
58+
public static MacroTriger Deserialize(byte[] json)
59+
{
60+
var t = JsonSerializer.Deserialize<MacroTriger>(json, JsonOpts);
61+
if (t?.Obj != null)
62+
{
63+
for (int i = 0; i < t.Obj.Length; i++)
64+
{
65+
if (t.Obj[i] is JsonElement e)
66+
{
67+
t.Obj[i] = e.ValueKind switch
68+
{
69+
JsonValueKind.Number => e.TryGetInt64(out var l) ? l : e.GetDouble(),
70+
JsonValueKind.String => e.GetString(),
71+
JsonValueKind.True => true,
72+
JsonValueKind.False => false,
73+
JsonValueKind.Null => null,
74+
_ => e.ToString()
75+
};
76+
}
77+
}
78+
}
79+
return t;
80+
}
1781
}
1882

19-
[Serializable()]
83+
[Serializable]
2084
public class MacroBase
2185
{
86+
// mainObject は派生クラス側 (PDIndexer / ReciPro / IPAnalyzer の Macro) から
87+
// FormMain インスタンスを保持するために使われる。dynamic は scope object 経由で
88+
// SetMacroToMenu を呼ぶための簡易ディスパッチ用。完全 typed 化はスコープ外。
2289
public dynamic mainObject;
23-
public string[] Help => [.. help];
2490
public string ScopeName = "";
2591
public List<string> help = [];
92+
public string[] Help => [.. help];
2693

2794
public MacroBase(dynamic _main, string scopeName)
2895
{
2996
mainObject = _main;
3097
ScopeName = scopeName;
3198
}
3299

33-
public void SetMacroToMenu(string[] name)
34-
{
35-
mainObject.SetMacroToMenu(name);
36-
}
37-
38-
100+
// 260414Cl virtual 化 (旧: 非 virtual)。FormMacro.obj が MacroBase 型に
101+
// なったあとも、IPAnalyzer 側 Macro が override で固有処理を維持できるように。
102+
public virtual void SetMacroToMenu(string[] name) => mainObject.SetMacroToMenu(name);
39103
}
40104

41-
[Serializable()]
42-
public class MacroSub
105+
[Serializable]
106+
public class MacroSub(Control _context)
43107
{
44-
private readonly Control context;
45-
public MacroSub(Control _context)
46-
{
47-
context = _context;
48-
}
49-
50-
//スレッド間で安全にコントロールを操作する、関数群
51-
public Type Execute<Type>(Expression<Func<Type>> expression) => Execute<Type>(context, expression.Compile(), null);
52-
53-
public void Execute(Expression<Action> expression) => Execute(context, expression.Compile(), null);
54-
55-
//public bool Execute(Func<bool> func) => Execute<bool>(func);
56-
57-
//public string Execute(Func<string> func) => Execute<string>(func);
58-
59-
//public string[] Execute(Func<string[]> func) => Execute<string[]>(func);
60-
61-
// public double[] Execute(Func<double[]> func) => Execute<double[]>(func);
62-
63-
//public int[] Execute(Func<int[]> func) => Execute<int[]>(func);
64-
65-
//public int Execute(Func<int> func) => Execute<int>(func);
66-
67-
//public double Execute(Func<double> func) => Execute<double>(func);
68-
69-
public static Type Execute<Type>(Control _context, Delegate process) => Execute<Type>(_context, process, null);
70-
71-
public Type Execute<Type>(Delegate process) => Execute<Type>(context, process, null);
72-
73-
public static void Execute(Control _context, Delegate process) => Execute(_context, process, null);
74-
75-
public void Execute(Delegate process) => Execute(context, process, null);
76-
77-
public static Type Execute<Type>(Control _context, Delegate process, params object[] args)
78-
{
79-
#region
80-
/*if (context == null)
81-
{
82-
throw new ArgumentNullException("context");
83-
}
84-
if (process == null)
85-
{
86-
throw new ArgumentNullException("process");
87-
}
108+
private readonly Control context = _context;
88109

89-
if (!(context.IsHandleCreated))
90-
{
91-
return null;
92-
}
93-
*/
94-
#endregion
95-
return _context.InvokeRequired ? (Type)_context.Invoke(process, args) : (Type)process.DynamicInvoke(args);
96-
}
97-
98-
public static void Execute(Control _context, Delegate process, params object[] args)
99-
{
100-
#region
101-
/*if (context == null)
102-
{
103-
throw new ArgumentNullException("context");
104-
}
105-
if (process == null)
106-
{
107-
throw new ArgumentNullException("process");
108-
}
110+
// 260414Cl 全面整理:
111+
// 旧: Execute<Type>(Expression<Func<Type>>), Execute(Expression<Action>),
112+
// Execute<Type>(Delegate), Execute(Delegate), 静的バリアント, params 付き等
113+
// 計 8 オーバーロードを定義していたが、実際に呼ばれていたのは Expression 版のみ。
114+
// Func<T> / Action 版を併存させると lambda 引数で CS0121 曖昧性エラーになるため、
115+
// Expression 版を撤去し以下 2 つに集約する。
109116

110-
if (!(context.IsHandleCreated))
111-
{
112-
return null;
113-
}
114-
*/
115-
#endregion
116-
if (_context.InvokeRequired)
117-
_context.Invoke(process, args);
118-
else
119-
process.DynamicInvoke(args);
120-
}
117+
public T Execute<T>(Func<T> func)
118+
=> context.InvokeRequired ? (T)context.Invoke(func) : func();
121119

122-
public Type Execute<Type>(Delegate process, params object[] args)
120+
public void Execute(Action action)
123121
{
124-
#region
125-
/*if (context == null)
126-
{
127-
throw new ArgumentNullException("context");
128-
}
129-
if (process == null)
130-
{
131-
throw new ArgumentNullException("process");
132-
}
133-
134-
if (!(context.IsHandleCreated))
135-
{
136-
return null;
137-
}
138-
*/
139-
#endregion
140-
return context.InvokeRequired ? (Type)context.Invoke(process, args) : (Type)process.DynamicInvoke(args);
141-
}
142-
143-
public void Execute(Delegate process, params object[] args)
144-
{
145-
#region
146-
/*if (context == null)
147-
{
148-
throw new ArgumentNullException("context");
149-
}
150-
if (process == null)
151-
{
152-
throw new ArgumentNullException("process");
153-
}
154-
155-
if (!(context.IsHandleCreated))
156-
{
157-
return null;
158-
}
159-
*/
160-
#endregion
161122
if (context.InvokeRequired)
162-
context.Invoke(process, args);
123+
context.Invoke(action);
163124
else
164-
process.DynamicInvoke(args);
125+
action();
165126
}
166127
}
167128

168129
#region HelpAttribute
169130

170131
[AttributeUsage(AttributeTargets.All)]
171-
public class HelpAttribute : System.Attribute
132+
public class HelpAttribute(string text, string arg = "") : Attribute
172133
{
173-
public string Text;
174-
public string Argument;
175-
public HelpAttribute(string text, string arg="") { Text = text; Argument = arg; }
134+
public string Text = text;
135+
public string Argument = arg;
136+
176137
public static List<string> GenerateHelpText(Type type, string name)
177138
{
178139
var strList = new List<string>();
179-
var header = type.Namespace + "." + name + ".";
180140

181-
if (header.Contains("PDIndexer")) header = header.Replace("PDIndexer", "PDI");
182-
if (header.Contains("IPAnalyzer")) header = header.Replace("IPAnalyzer", "IPA");
141+
// 260414Cl name が null/空 の場合 "PDI..MethodName" のような空セクションが
142+
// 出ないよう連結を調整。root クラス自身に対しても呼べるようになった。
143+
var ns = type.Namespace ?? "";
144+
if (ns.Contains("PDIndexer")) ns = ns.Replace("PDIndexer", "PDI");
145+
if (ns.Contains("IPAnalyzer")) ns = ns.Replace("IPAnalyzer", "IPA");
146+
var header = string.IsNullOrEmpty(name) ? ns + "." : ns + "." + name + ".";
183147

184148
foreach (var p in type.GetProperties().Where(e => e.GetCustomAttribute<HelpAttribute>() != null))
185149
strList.Add(header + p.Name + "#" + p.GetCustomAttribute<HelpAttribute>().Text);
@@ -188,7 +152,6 @@ public static List<string> GenerateHelpText(Type type, string name)
188152

189153
return strList;
190154
}
191-
192155
}
193156

194-
#endregion
157+
#endregion

Crystallography/Crystallography.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<OutputType>Library</OutputType>
55
<TargetFramework>net10.0-windows</TargetFramework>
66
<UseWindowsForms>true</UseWindowsForms>
7-
<AssemblyVersion>2026.4.13.0650</AssemblyVersion>
8-
<FileVersion>2026.4.13.0650</FileVersion>
7+
<AssemblyVersion>2026.4.14.0153</AssemblyVersion>
8+
<FileVersion>2026.4.14.0153</FileVersion>
99
<!-- <SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion> --><!-- 260405Cl .NET 10はWindows7非対応のため削除 -->
1010
<Platforms>x64</Platforms>
1111
<PublishReadyToRun>true</PublishReadyToRun>

IPAnalyzer/FormMain.Designer.cs

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)