PropertyGrid は非常に強力なコントロールです。このコントロールをプロパティ設定パネルとして使用する利点の 1 つは、PropertyGrid が UI のプレゼンテーションではなく、コードにのみ注目する必要があることです。デフォルトでは変数タイプ。しかし、これは問題も引き起こします。つまり、主に、たとえば、int 変数を設定するために Slider コントロールを使用する必要がある場合、ニーズに応じてコントロールを選択できないため、コントロールの使用はそれほど柔軟ではありません。 PropertyGrid のデフォルトは次のとおりです。テンプレート セレクターはサポートされていません。基本的に IWindowFromService インターフェイスを使用した WinForm の実装を紹介する多くの情報をインターネット上で見つけましたが、WPF に適した適切なデモが見つかりませんでした。その後、DEVExpress の公式デモを参照してデモを作成しました。 WPF および DEV 16.2 では、PropertyGrid デモは基本的に上記の機能を実装します。
これを実現するには、この記事の中心となるコードでもある DataTemplateSeletor クラスをカスタマイズする必要があります。
1. CustomPropertyGrid カスタム コントロールを作成します:


1 <usercontrol>12 <usercontrol.resources>13 <resourcedictionary>14 <resourcedictionary.mergeddictionaries>15 <!-- 资源字典 -->16 <resourcedictionary></resourcedictionary>17 </resourcedictionary.mergeddictionaries>18 </resourcedictionary>19 </usercontrol.resources>20 <grid>21 <!-- PropertyDefinitionStyle:定义属性描述的风格模板 -->22 <!-- PropertyDefinitionTemplateSelector:定义一个模板选择器,对应一个继承自DataTemplateSelector的类 -->23 <!-- PropertyDefinitionsSource:定义一个获取数据属性集合的类,对应一个自定义类(本Demo中对应DataEditorsViewModel) -->24 <propertygridcontrol25></propertygridcontrol25>34 </grid>35 </usercontrol>
このコントロールで使用されるリソース ディクショナリは次のとおりです:


1 <resourcedictionary>11 12 <dynamicallyassigndataeditorstemplateselector></dynamicallyassigndataeditorstemplateselector>13 <dataeditorsviewmodel></dataeditorsviewmodel>14 15 <datatemplate>16 <richtextbox17></richtextbox17>25 </datatemplate>26 <datatemplate>27 <richtextbox28></richtextbox28>36 </datatemplate>37 38 <!-- 设置控件的全局样式和数据绑定 -->39 <style>40 <Setter Property="Path" Value="{Binding Name}" />41 <!--<Setter Property="Header" Value="{Binding Converter={StaticResource PropertyDescriptorToDisplayNameConverter}}"/>-->42 <Setter Property="Description" Value="{Binding}" />43 <Setter Property="DescriptionTemplate" Value="{StaticResource descriptionTemplate}" />44 </style>45 <style>46 <Setter Property="ShowSelectedRowHeader" Value="False" />47 <Setter Property="MinHeight" Value="70" />48 </style>49 50 <style>51 <Setter Property="Margin" Value="2" />52 </style>53 <style>54 <Setter Property="IsTextEditable" Value="False" />55 <Setter Property="ApplyItemTemplateToSelectedItem" Value="True" />56 <Setter Property="Margin" Value="2" />57 </style>58 59 <!-- 测试直接从DataTemplate获取控件 -->60 <datatemplate>61 <!--<dxprg:PropertyDefinition>62 <dxprg:PropertyDefinition.CellTemplate>-->63 <!--<DataTemplate>-->64 <stackpanel>65 <slider66></slider66>69 <textblock></textblock>70 </stackpanel>71 <!--</DataTemplate>-->72 <!--</dxprg:PropertyDefinition.CellTemplate>73 </dxprg:PropertyDefinition>-->74 </datatemplate>75 76 <datatemplate>77 <textblock78></textblock78>82 </datatemplate>83 </resourcedictionary>
2. を書きます。対応するテンプレートの選択 クラス DynamicallyAssignDataEditorsTemplateSelector:


1 using DevExpress.Xpf.Editors; 2 using DevExpress.Xpf.PropertyGrid; 3 using System.ComponentModel; 4 using System.Reflection; 5 using System.Windows; 6 using System.Windows.Controls; 7 using System.Windows.Data; 8 9 namespace PropertyGridDemo.PropertyGridControl 10 { 11 public class DynamicallyAssignDataEditorsTemplateSelector : DataTemplateSelector 12 { 13 private PropertyDescriptor _property = null; 14 private RootPropertyDefinition _element = null; 15 private PropertyDataContext _propertyDataContext => App.PropertyGridDataContext; 16 17 /// <summary> 18 /// 当重写在派生类中,返回根据自定义逻辑的 <see></see> 。 19 /// </summary> 20 /// <param>数据对象可以选择模板。 21 /// <param>数据对象。 22 /// <returns> 23 /// 返回 <see></see> 或 null。默认值为 null。 24 /// </returns> 25 public override DataTemplate SelectTemplate(object item, DependencyObject container) 26 { 27 _element = (RootPropertyDefinition)container; 28 DataTemplate resource = TryCreateResource(item); 29 return resource ?? base.SelectTemplate(item, container); 30 } 31 32 /// <summary> 33 /// Tries the create resource. 34 /// </summary> 35 /// <param>The item. 36 /// <returns></returns> 37 private DataTemplate TryCreateResource(object item) 38 { 39 if (!(item is PropertyDescriptor)) return null; 40 PropertyDescriptor pd = (PropertyDescriptor)item; 41 _property = pd; 42 var customUIAttribute = (CustomUIAttribute)pd.Attributes[typeof(CustomUIAttribute)]; 43 if (customUIAttribute == null) return null; 44 var customUIType = customUIAttribute.CustomUI; 45 return CreatePropertyDefinitionTemplate(customUIAttribute); 46 } 47 48 /// <summary> 49 /// Gets the data context. 50 /// </summary> 51 /// <param>Name of the data context property. 52 /// <returns></returns> 53 private object GetDataContext(string dataContextPropertyName) 54 { 55 PropertyInfo property = _propertyDataContext?.GetType().GetProperty(dataContextPropertyName); 56 if (property == null) return null; 57 return property.GetValue(_propertyDataContext, null); 58 } 59 60 /// <summary> 61 /// Creates the slider data template. 62 /// </summary> 63 /// <param>The custom UI attribute. 64 /// <returns></returns> 65 private DataTemplate CreateSliderDataTemplate(CustomUIAttribute customUIAttribute) 66 { 67 DataTemplate ct = new DataTemplate(); 68 ct.VisualTree = new FrameworkElementFactory(typeof(StackPanel)); 69 ct.VisualTree.SetValue(StackPanel.DataContextProperty, GetDataContext(customUIAttribute.DataContextPropertyName)); 70 71 FrameworkElementFactory sliderFactory = new FrameworkElementFactory(typeof(Slider)); 72 sliderFactory.SetBinding(Slider.MaximumProperty, new Binding(nameof(SliderUIDataContext.Max))); 73 sliderFactory.SetBinding(Slider.MinimumProperty, new Binding(nameof(SliderUIDataContext.Min))); 74 sliderFactory.SetBinding(Slider.SmallChangeProperty, new Binding(nameof(SliderUIDataContext.SmallChange))); 75 sliderFactory.SetBinding(Slider.LargeChangeProperty, new Binding(nameof(SliderUIDataContext.LargeChange))); 76 sliderFactory.SetBinding(Slider.ValueProperty, new Binding(nameof(SliderUIDataContext.Value))); 77 ct.VisualTree.AppendChild(sliderFactory); 78 79 FrameworkElementFactory textFacotry = new FrameworkElementFactory(typeof(TextBlock), "TextBlock"); 80 textFacotry.SetValue(TextBlock.TextProperty, new Binding(nameof(SliderUIDataContext.Value))); 81 //textBoxFactory.AddHandler(TextBox.IsVisibleChanged, new DependencyPropertyChangedEventHandler(SearchBoxVisibleChanged)); 82 ct.VisualTree.AppendChild(textFacotry); 83 ct.Seal(); 84 return ct; 85 } 86 87 /// <summary> 88 /// Creates the ComboBox edit template. 89 /// </summary> 90 /// <param>The custom UI attribute. 91 /// <returns></returns> 92 private DataTemplate CreateComboBoxEditTemplate(CustomUIAttribute customUIAttribute) 93 { 94 DataTemplate template = new DataTemplate(); 95 template.VisualTree = new FrameworkElementFactory(typeof(DockPanel)); 96 template.VisualTree.SetValue(DockPanel.DataContextProperty, GetDataContext(customUIAttribute.DataContextPropertyName)); 97 98 FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock)) ; 99 textFactory.SetValue(TextBlock.TextProperty, new Binding(nameof(ComboBoxEditDataContext.Name)));100 template.VisualTree.AppendChild(textFactory);101 102 FrameworkElementFactory comboBoxEditFactory = new FrameworkElementFactory(typeof(ComboBoxEdit));103 comboBoxEditFactory.SetBinding(ComboBoxEdit.ItemsSourceProperty, new Binding(nameof(ComboBoxEditDataContext.ItemSource)));104 comboBoxEditFactory.SetBinding(ComboBoxEdit.EditValueProperty, new Binding(nameof(ComboBoxEditDataContext.EditValue)));105 comboBoxEditFactory.SetBinding(ComboBoxEdit.SelectedIndexProperty, new Binding(nameof(ComboBoxEditDataContext.SelectedIndex)));106 comboBoxEditFactory.SetValue(ComboBoxEdit.ItemTemplateProperty, (DataTemplate)_element.TryFindResource("ComboBoxEditItemTemplate"));107 template.VisualTree.AppendChild(comboBoxEditFactory);108 template.Seal();109 return template;110 }111 112 /// <summary>113 /// Creates the property definition template.114 /// </summary>115 /// <param>The custom UI attribute.116 /// <returns></returns>117 private DataTemplate CreatePropertyDefinitionTemplate(CustomUIAttribute customUIAttribute)118 {119 DataTemplate dataTemplate = new DataTemplate();120 DataTemplate cellTemplate = null;//单元格模板121 FrameworkElementFactory factory = new FrameworkElementFactory(typeof(PropertyDefinition));122 dataTemplate.VisualTree = factory;123 switch (customUIAttribute.CustomUI)124 {125 case CustomUITypes.Slider:126 cellTemplate = CreateSliderDataTemplate(customUIAttribute); break;127 //cellTemplate = (DataTemplate)_element.TryFindResource("SliderTemplate");break;128 case CustomUITypes.ComboBoxEit:129 cellTemplate = CreateComboBoxEditTemplate(customUIAttribute);break;130 131 }132 133 if (cellTemplate != null)134 {135 factory.SetValue(PropertyDefinition.CellTemplateProperty, cellTemplate);136 dataTemplate.Seal();137 138 }139 else140 {141 return null;142 }143 return dataTemplate;144 }145 }146 }


using System.Collections.Generic;using System.ComponentModel;using System.Linq;namespace PropertyGridDemo.PropertyGridControl {/// <summary>///初始化所有属性并调用模板选择器进行匹配/// </summary>public class DataEditorsViewModel {public IEnumerable<propertydescriptor> Properties { get { return TypeDescriptor.GetProperties(typeof(TestPropertyGrid)).Cast<propertydescriptor>(); } } } }</propertydescriptor></propertydescriptor>
3. プロパティを書き込みます。テンプレートの構築に使用できる CustomUIType:


using System;namespace PropertyGridDemo.PropertyGridControl {public class CustomUIType { }public enum CustomUITypes { Slider, ComboBoxEit, SpinEdit, CheckBoxEdit } [AttributeUsage(AttributeTargets.Property)]internal class CustomUIAttribute : Attribute {public string DataContextPropertyName { get; set; }public CustomUITypes CustomUI { get; set; }/// <summary>/// 自定义控件属性构造函数/// </summary>/// <param>The UI types./// <param>Name of the data context property.internal CustomUIAttribute(CustomUITypes uiTypes, string dataContextPropertyName) { CustomUI = uiTypes; DataContextPropertyName = dataContextPropertyName; } } }
4 .対応する DataContext クラスを記述します TestPropertyGrid:


1 using DevExpress.Mvvm.DataAnnotations; 2 using System; 3 using System.ComponentModel; 4 using System.ComponentModel.DataAnnotations; 5 using System.Timers; 6 using System.Windows; 7 8 namespace PropertyGridDemo.PropertyGridControl 9 { 10 [MetadataType(typeof(DynamicallyAssignDataEditorsMetadata))] 11 public class TestPropertyGrid : PropertyDataContext 12 { 13 private double _count = 0; 14 private SliderUIDataContext _countSource = null; 15 private ComboBoxEditDataContext _comboSource = null; 16 private double _value=1; 17 18 public TestPropertyGrid() 19 { 20 Password = "1111111"; 21 Notes = "Hello"; 22 Text = "Hello hi"; 23 } 24 25 [Browsable(false)] 26 public SliderUIDataContext CountSource 27 { 28 get 29 { 30 if (_countSource != null) 31 { 32 33 return _countSource; 34 } 35 else 36 { 37 _countSource = new SliderUIDataContext(0, 100, Count, 0.1, 1); 38 _countSource.PropertyChanged += (object o, PropertyChangedEventArgs e) => 39 { 40 this.Count = _countSource.Value; 41 }; 42 return _countSource; 43 } 44 } 45 } 46 47 [Browsable(false)] 48 public ComboBoxEditDataContext ComboSource 49 { 50 get 51 { 52 if(_comboSource==null) 53 { 54 _comboSource =new ComboBoxEditDataContext(ComboBoxEditItemSource.TestItemSource,Value); 55 _comboSource.PropertyChanged += (object o, PropertyChangedEventArgs e) => 56 { 57 this.Value =Convert.ToDouble(_comboSource.EditValue.Item2); 58 }; 59 60 } 61 return _comboSource; 62 } 63 } 64 65 [Display(Name = "SliderEdit", GroupName = "CustomUI")] 66 [CustomUI(CustomUITypes.Slider, nameof(CountSource))] 67 public double Count 68 { 69 get => _count; 70 set 71 { 72 _count = value; 73 CountSource.Value = value; 74 RaisePropertyChanged(nameof(Count)); 75 } 76 } 77 78 [Display(Name = "ComboBoxEditItem", GroupName = "CustomUI")] 79 [CustomUI(CustomUITypes.ComboBoxEit, nameof(ComboSource))] 80 public double Value 81 { 82 get => _value; 83 set 84 { 85 if (_value == value) return; 86 _value = value; 87 //ComboSource.Value = value; 88 RaisePropertyChanged(nameof(Value)); 89 } 90 } 91 92 [Display(Name = "Password", GroupName = "DefaultUI")] 93 public string Password { get; set; } 94 95 [Display(Name = "TextEdit", GroupName = "DefaultUI")] 96 public string Text { get; set; } 97 98 [Display(Name = "Notes", GroupName = "DefaultUI")] 99 public string Notes { get; set; }100 101 102 [Display(Name = "Double", GroupName = "DefaultUI")]103 [DefaultValue(1)]104 public double TestDouble { get; set; }105 106 [Display(Name = "Items", GroupName = "DefaultUI")]107 [DefaultValue(Visibility.Visible)]108 public Visibility TestItems { get; set; }109 }110 111 public static class DynamicallyAssignDataEditorsMetadata112 {113 public static void BuildMetadata(MetadataBuilder<testpropertygrid> builder)114 {115 builder.Property(x => x.Password)116 .PasswordDataType();117 118 builder.Property(x => x.Notes)119 .MultilineTextDataType();120 }121 }122 }</testpropertygrid>
このクラスで使用されるその他のクラスには主に次のものが含まれており、次のクラスは主にデータ バインディングに使用されます:




using System;using System.Linq;namespace PropertyGridDemo.PropertyGridControl {public class ComboBoxEditDataContext:PropertyDataContext {private Tuple<string>[] _itemSource;private Tuple<string> _editValue;private int _selectedIndex;/// <summary>/// Initializes a new instance of the <see></see> class./// </summary>/// <param>The item source./// <param>The edit value.public ComboBoxEditDataContext(Tuple<string>[] itemSource,Tuple<string> editValue) { _itemSource = itemSource; _editValue = _itemSource.FirstOrDefault(x => x?.Item1.ToString() == editValue?.Item1.ToString() && x?.Item2?.ToString() == x?.Item2?.ToString()); }/// <summary>/// Initializes a new instance of the <see></see> class./// </summary>/// <param>The item source./// <param>The value.public ComboBoxEditDataContext(Tuple<string>[] itemSource, object value) { _itemSource = itemSource; _editValue = _itemSource.FirstOrDefault(x => x?.Item2.ToString() == value.ToString() ); }public string Name {get;set; }/// <summary>/// Gets or sets the item source./// </summary>/// <value>/// The item source./// </value>public Tuple<string>[] ItemSource {get => _itemSource;set{//if (_itemSource == value) return;_itemSource = value; RaisePropertyChanged(nameof(ItemSource)); } }/// <summary>/// Gets or sets the edit value./// </summary>/// <value>/// The edit value./// </value>public Tuple<string> EditValue {get => _editValue;set{if (_editValue == value) return; _editValue = value; RaisePropertyChanged(nameof(EditValue)); } }public object Value {set{ EditValue = ItemSource.FirstOrDefault(x => x.Item2.Equals(value)); } }/// <summary>/// Gets or sets the index of the selected./// </summary>/// <value>/// The index of the selected./// </value>public int SelectedIndex {get => _selectedIndex;set{if (_selectedIndex == value || value==-1) return; _selectedIndex = value; EditValue = ItemSource[value]; RaisePropertyChanged(nameof(SelectedIndex)); } } } }</string></string></string></string></string></string></string>


using System.ComponentModel;namespace PropertyGridDemo.PropertyGridControl {public class PropertyDataContext:INotifyPropertyChanged {/// <summary>/// 在更改属性值时发生。/// </summary>public event PropertyChangedEventHandler PropertyChanged;/// <summary>/// 触发属性变化/// </summary>/// <param>public virtual void RaisePropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }


using System;namespace PropertyGridDemo.PropertyGridControl {internal static class ComboBoxEditItemSource {internal static Tuple<string>[] TestItemSource = new Tuple<string>[] {new Tuple<string>("1",1),new Tuple<string>("2",2),new Tuple<string>("3",3) }; } }</string></string></string></string></string>
5.将以上的CustomPropertyGrid丢进容器中即可,这里我直接用Mainwindow来演示:


1 <window>14 <grid>15 <grid.columndefinitions>16 <columndefinition></columndefinition>17 <columndefinition></columndefinition>18 </grid.columndefinitions>19 20 <textbox21></textbox21>25 <custompropertygrid></custompropertygrid>26 </grid>27 </window>
运行示意图:
以上就是自定义PropertyGrid控件的实现代码,本人只实现了简单的Slider和ComboBoxEdit控件,实际上可以根据自己的需要仿照以上的方法扩展到其他控件,这个就看需求了。
个人感觉以上方案还是有所欠缺,主要是自定义控件的模板是由代码生成的,如果可以直接从资源文件中读取将会更加方便,不过本人尝试了几次并不能成功的实现数据的绑定,如果大家有什么好的解决方案欢迎在评论区留言,也欢迎大家在评论区进行讨论。
以上内容均为原创,转发请注明出处,谢谢!
以上が非常に強力なコントロール PropertyGridの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

c#.netissuitableforenterprise-levelApplicationsとsystemduetoitsSystemdutyping、richlibraries、androbustperformance.

.NETでのC#のプログラミングプロセスには、次の手順が含まれます。1)C#コードの作成、2)中間言語(IL)にコンパイルし、3).NETランタイム(CLR)によって実行される。 .NETのC#の利点は、デスクトップアプリケーションからWebサービスまでのさまざまな開発シナリオに適した、最新の構文、強力なタイプシステム、および.NETフレームワークとの緊密な統合です。

C#は、Microsoftによって開発された最新のオブジェクト指向プログラミング言語であり、.NETフレームワークの一部として開発されています。 1.C#は、カプセル化、継承、多型を含むオブジェクト指向プログラミング(OOP)をサポートしています。 2。C#の非同期プログラミングは非同期を通じて実装され、適用応答性を向上させるためにキーワードを待ちます。 3. LINQを使用してデータ収集を簡潔に処理します。 4.一般的なエラーには、null参照の例外と、範囲外の例外インデックスが含まれます。デバッグスキルには、デバッガーと例外処理の使用が含まれます。 5.パフォーマンスの最適化には、StringBuilderの使用と、不必要な梱包とボクシングの回避が含まれます。

C#.NETアプリケーションのテスト戦略には、ユニットテスト、統合テスト、エンドツーエンドテストが含まれます。 1.単位テストにより、コードの最小ユニットがMSTEST、ヌニット、またはXUNITフレームワークを使用して独立して動作することを保証します。 2。統合テストでは、一般的に使用されるシミュレートされたデータと外部サービスを組み合わせた複数のユニットの機能を検証します。 3.エンドツーエンドのテストでは、ユーザーの完全な操作プロセスをシミュレートし、通常、セレンは自動テストに使用されます。

C#シニア開発者とのインタビューでは、非同期プログラミング、LINQ、.NETフレームワークの内部作業原則などのコア知識をマスターする必要があります。 1.非同期プログラミングは、非同期を通じて操作を簡素化し、アプリケーションの応答性を向上させるのを待ちます。 2.LinqはSQLスタイルでデータを操作し、パフォーマンスに注意を払います。 3.ネットフレームワークのCLRはメモリを管理し、ガベージコレクションに注意して使用する必要があります。

C#.NETインタビューの質問と回答には、基本的な知識、コアの概念、高度な使用が含まれます。 1)基本知識:C#は、Microsoftが開発したオブジェクト指向言語であり、主に.NETフレームワークで使用されています。 2)コアの概念:委任とイベントは動的な結合方法を可能にし、LINQは強力なクエリ関数を提供します。 3)高度な使用:非同期プログラミングは応答性を向上させ、式ツリーは動的コード構造に使用されます。

C#.NETは、その強力なエコシステムと豊富なサポートのため、マイクロサービスを構築するために人気のある選択肢です。 1)asp.netcoreを使用してRestfulapiを作成して、順序の作成とクエリを処理します。 2)GRPCを使用して、マイクロサービス間の効率的な通信を実現し、注文サービスを定義および実装します。 3)Dockerコンテナ化されたマイクロサービスを介して展開と管理を簡素化します。

C#および.NETのセキュリティベストプラクティスには、入力検証、出力エンコード、例外処理、認証と承認が含まれます。 1)正規表現または組み込みのメソッドを使用して入力を検証して、悪意のあるデータがシステムに入るのを防ぎます。 2)XSS攻撃を防ぐための出力エンコード、httputility.htmlencodeメソッドを使用します。 3)例外処理により、情報の漏れが回避され、エラーが記録されますが、詳細情報はユーザーに返されません。 4)ASP.Netidentityおよび請求に基づく許可を使用して、不正アクセスから申請を保護します。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

SecLists
SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

PhpStorm Mac バージョン
最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

SAP NetWeaver Server Adapter for Eclipse
Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

DVWA
Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)
