2008-09-09 31 views
57

Tôi không chắc liệu có thể thay đổi tham số của thuộc tính trong thời gian chạy không? Ví dụ, trong một hội đồng tôi có lớp sauThay đổi tham số của Thuộc tính tại thời gian chạy

public class UserInfo 
{ 
    [Category("change me!")] 
    public int Age 
    { 
     get; 
     set; 
    } 
    [Category("change me!")] 
    public string Name 
    { 
     get; 
     set; 
    } 
} 

Đây là một lớp học được cung cấp bởi một nhà cung cấp bên thứ ba và tôi không thể thay đổi mã. Nhưng bây giờ tôi thấy rằng các mô tả ở trên không chính xác và tôi muốn thay đổi tên danh mục "thay đổi tôi" thành tên khác khi tôi liên kết một phiên bản của lớp trên với lưới thuộc tính.

Tôi có thể biết cách thực hiện việc này không?

Trả lời

0

Tôi thực sự không nghĩ như vậy, trừ khi có một số phản ánh sôi nổi có thể kéo nó đi. Các đồ trang trí bất động sản được thiết lập tại thời gian biên dịch và kiến ​​thức của tôi được cố định

24

Vâng, bạn học hỏi những điều mới mẻ mỗi ngày, dường như tôi đã nói dối:

gì không thường nhận ra là bạn có thể thay đổi thuộc tính Ví dụ giá trị khá dễ dàng khi chạy. Lý do là, trong các khóa học , rằng các trường hợp của các lớp thuộc tính được tạo là các đối tượng hoàn toàn bình thường và có thể là được sử dụng không hạn chế. Ví dụ, chúng tôi có thể nhận được các đối tượng:

ASCII[] attrs1=(ASCII[]) 
    typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); 

... thay đổi giá trị của biến công cộng của nó và cho thấy rằng nó đã thay đổi:

attrs1[0].MyData="A New String"; 
MessageBox.Show(attrs1[0].MyData); 

... và cuối cùng tạo ra một ví dụ và cho thấy rằng nó giá trị là không thay đổi:

ASCII[] attrs3=(ASCII[]) 
    typeof(MyClass).GetCustomAttributes(typeof(ASCII), false); 
MessageBox.Show(attrs3[0].MyData); 

http://www.vsj.co.uk/articles/display.asp?id=713

+11

Vâng, không thực sự. Bạn có thể tạo một thể hiện của đối tượng thuộc tính và sửa đổi nó, nhưng nó sẽ không ảnh hưởng đến bất cứ điều gì bằng cách sử dụng các thuộc tính được đánh dấu trên thuộc tính (vì chúng sẽ nhận được cá thể không thay đổi của riêng chúng). – denver

1

Bạn đã giải quyết được sự cố chưa?

Dưới đây là các bước có thể có để đạt được giải pháp có thể chấp nhận được.

  1. Cố gắng tạo lớp con, xác định lại tất cả thuộc tính mà bạn cần thay đổi thuộc tính [Category] (đánh dấu chúng là new). Ví dụ:
public class UserInfo 
{ 
[Category("Must change")] 
public string Name { get; set; } 
} 

public class NewUserInfo : UserInfo 
{ 
public NewUserInfo(UserInfo user) 
{ 
// transfer all the properties from user to current object 
} 

[Category("Changed")] 
public new string Name { 
get {return base.Name; } 
set { base.Name = value; } 
} 

public static NewUserInfo GetNewUser(UserInfo user) 
{ 
return NewUserInfo(user); 
} 
} 

void YourProgram() 
{ 
UserInfo user = new UserInfo(); 
... 

// Bind propertygrid to object 

grid.DataObject = NewUserInfo.GetNewUser(user); 

... 

} 

Sau đó Edit:này là một phần của giải pháp không phải là hoàn toàn khả thi nếu bạn có một số lượng lớn các tính năng mà bạn có thể cần phải viết lại các thuộc tính. Đây là nơi phần hai được đặt vào:

  1. Tất nhiên, điều này sẽ không giúp ích cho lớp học, hoặc nếu bạn có nhiều đối tượng (và thuộc tính) . Bạn sẽ cần phải tạo một lớp proxy hoàn toàn tự động để lớp của bạn và tạo ra một lớp động, áp dụng các thuộc tính và tất nhiên tạo một kết nối giữa hai lớp.Điều này phức tạp hơn một chút, nhưng cũng có thể đạt được. Chỉ cần sử dụng sự phản chiếu và bạn đang đi đúng con đường.
+0

Bogdan, Tôi sợ rằng việc phân lớp lớp và thực hiện tất cả các định nghĩa là không thực tế, để nói rằng ít nhất. – Graviton

+0

Nếu bạn đang phân lớp, thì bạn sẽ phải tự động tạo lại tất cả các thuộc tính và thay thế các thuộc tính cũ. Đây là giải pháp dễ nhất nếu bạn có thể phân lớp. Ý tưởng chính là tạo ra một proxy tự động (sử dụng một loại động), và thay thế các thuộc tính trên bay. –

0

Trong thời gian có nghĩa là tôi đã đi đến một giải pháp một phần, có nguồn gốc từ các bài viết sau:

  1. ICustomTypeDescriptor, Part 1
  2. ICustomTypeDescriptor, Part 2
  3. Add (Remove) Items to (from) PropertyGrid at Runtime

Về cơ bản bạn sẽ tạo một lớp chung CustomTypeDescriptorWithResources<T>, có thể nhận được các thuộc tính thông qua phản ánh ion và tải DescriptionCategory từ một tập tin (Tôi cho rằng bạn cần để hiển thị văn bản cục bộ, do đó bạn có thể sử dụng một tập tin nguồn (.resx))

4

Bạn có thể phân lớp hầu hết các thuộc tính chung khá dễ dàng để cung cấp khả năng mở rộng này:

using System; 
using System.ComponentModel; 
using System.Windows.Forms; 
class MyCategoryAttribute : CategoryAttribute { 
    public MyCategoryAttribute(string categoryKey) : base(categoryKey) { } 

    protected override string GetLocalizedString(string value) { 
     return "Whad'ya know? " + value; 
    } 
} 

class Person { 
    [MyCategory("Personal"), DisplayName("Date of Birth")] 
    public DateTime DateOfBirth { get; set; } 
} 

static class Program { 
    [STAThread] 
    static void Main() { 
     Application.EnableVisualStyles(); 
     Application.Run(new Form { Controls = { 
      new PropertyGrid { Dock = DockStyle.Fill, 
       SelectedObject = new Person { DateOfBirth = DateTime.Today} 
      }}}); 
    } 
} 

có những lựa chọn phức tạp hơn có liên quan đến văn bản tùy chỉnh PropertyDescriptor s, tiếp xúc qua TypeConverter, ICustomTypeDescriptor hoặc TypeDescriptionProvider - nhưng đó là thường quá mức cần thiết.

+0

Nhưng Marc, ông nói rằng ông không có quyền truy cập vào mã số – toddmo

7

Trong trường hợp bất kỳ ai khác đi xuống đại lộ này, câu trả lời là bạn có thể làm điều đó, với sự phản ánh, ngoại trừ bạn không thể vì có lỗi trong khuôn khổ. Dưới đây là cách bạn sẽ thực hiện:

Dim prop As PropertyDescriptor = TypeDescriptor.GetProperties(GetType(UserInfo))("Age") 
    Dim att As CategoryAttribute = DirectCast(prop.Attributes(GetType(CategoryAttribute)), CategoryAttribute) 
    Dim cat As FieldInfo = att.GetType.GetField("categoryValue", BindingFlags.NonPublic Or BindingFlags.Instance) 
    cat.SetValue(att, "A better description") 

Tất cả tốt và tốt, ngoại trừ thuộc tính danh mục được thay đổi cho tất cả các thuộc tính, không chỉ là 'Độ tuổi'.

+4

Tôi sẽ khó gọi nó là một lỗi nếu bạn đi can thiệp xung quanh trong "BindingFlags.NonPublic" lĩnh vực. –

+1

Tôi tin rằng đây thực sự là một lỗi vì nó cũng xảy ra với các thuộc tính công cộng, ngay cả khi bạn không sử dụng các trường "BindingFlags.NonPublic". Có ai biết nếu điều này đã được nâng lên? Một liên kết đến trang lỗi sẽ hữu ích. Sử dụng TypeDescriptor thay vì Reflection hoạt động hoàn hảo! – kkara

1

Cho rằng sản phẩm được chọn của PropertyGrid là "Age":

SetCategoryLabelViaReflection(MyPropertyGrid.SelectedGridItem.Parent, 
    MyPropertyGrid.SelectedGridItem.Parent.Label, "New Category Label"); 

đâu SetCategoryLabelViaReflection() được định nghĩa như sau:

private void SetCategoryLabelViaReflection(GridItem category, 
              string oldCategoryName, 
              string newCategoryName) 
{ 
    try 
    { 
     Type t = category.GetType(); 
     FieldInfo f = t.GetField("name", 
           BindingFlags.NonPublic | BindingFlags.Instance); 
     if (f.GetValue(category).Equals(oldCategoryName)) 
     { 
      f.SetValue(category, newCategoryName); 
     } 
    } 
    catch (Exception ex) 
    { 
     System.Diagnostics.Trace.Write("Failed Renaming Category: " + ex.ToString()); 
    } 
} 

As far as programatically thiết lập các mục đã chọn, các chủ đề chính trong đó bạn muốn thay đổi; có một số giải pháp đơn giản. Google "Đặt tiêu điểm cho thuộc tính PropertyGrid cụ thể".

-1

Bạn có thể thay đổi giá trị thuộc tính khi chạy ở cấp lớp (không phản đối):

var attr = TypeDescriptor.GetProperties(typeof(UserContact))["UserName"].Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute; 
attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(attr, username_readonly); 
2

Đáng tiếc là thuộc tính không có nghĩa là thay đổi khi chạy. Về cơ bản, bạn có hai tùy chọn:

  1. Tạo lại loại tương tự trên bay bằng cách sử dụng System.Reflection.Emit như được hiển thị bên dưới.

  2. Yêu cầu nhà cung cấp của bạn thêm chức năng này. Nếu bạn đang sử dụng Xceed.WpfToolkit.Extended, bạn có thể tải xuống mã nguồn từ here và dễ dàng triển khai giao diện như IResolveCategoryName có thể giải quyết thuộc tính khi chạy. Tôi đã làm nhiều hơn một chút, nó đã được khá dễ dàng để thêm nhiều chức năng hơn như giới hạn khi chỉnh sửa một giá trị số trong một DoubleUpDown bên trong PropertyGrid, vv

    namespace Xceed.Wpf.Toolkit.PropertyGrid 
    { 
        public interface IPropertyDescription 
        { 
         double MinimumFor(string propertyName); 
         double MaximumFor(string propertyName); 
         double IncrementFor(string propertyName); 
         int DisplayOrderFor(string propertyName); 
         string DisplayNameFor(string propertyName); 
         string DescriptionFor(string propertyName); 
         bool IsReadOnlyFor(string propertyName); 
        } 
    } 
    

Đối với tùy chọn đầu tiên: Đây tuy nhiên thiếu tài sản ràng buộc để phản ánh kết quả lại cho các đối tượng thực tế thích hợp đang được chỉnh sửa.

private static void CreatePropertyAttribute(PropertyBuilder propertyBuilder, Type attributeType, Array parameterValues) 
    { 
     var parameterTypes = (from object t in parameterValues select t.GetType()).ToArray(); 
     ConstructorInfo propertyAttributeInfo = typeof(RangeAttribute).GetConstructor(parameterTypes); 
     if (propertyAttributeInfo != null) 
     { 
      var customAttributeBuilder = new CustomAttributeBuilder(propertyAttributeInfo, 
       parameterValues.Cast<object>().ToArray()); 
      propertyBuilder.SetCustomAttribute(customAttributeBuilder); 
     } 
    } 
    private static PropertyBuilder CreateAutomaticProperty(TypeBuilder typeBuilder, PropertyInfo propertyInfo) 
    { 
     string propertyName = propertyInfo.Name; 
     Type propertyType = propertyInfo.PropertyType; 

     // Generate a private field 
     FieldBuilder field = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); 

     // Generate a public property 
     PropertyBuilder property = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, 
      null); 

     // The property set and property get methods require a special set of attributes: 
     const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig; 

     // Define the "get" accessor method for current private field. 
     MethodBuilder currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, getSetAttr, propertyType, Type.EmptyTypes); 

     // Intermediate Language stuff... 
     ILGenerator currGetIl = currGetPropMthdBldr.GetILGenerator(); 
     currGetIl.Emit(OpCodes.Ldarg_0); 
     currGetIl.Emit(OpCodes.Ldfld, field); 
     currGetIl.Emit(OpCodes.Ret); 

     // Define the "set" accessor method for current private field. 
     MethodBuilder currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, getSetAttr, null, new[] { propertyType }); 

     // Again some Intermediate Language stuff... 
     ILGenerator currSetIl = currSetPropMthdBldr.GetILGenerator(); 
     currSetIl.Emit(OpCodes.Ldarg_0); 
     currSetIl.Emit(OpCodes.Ldarg_1); 
     currSetIl.Emit(OpCodes.Stfld, field); 
     currSetIl.Emit(OpCodes.Ret); 

     // Last, we must map the two methods created above to our PropertyBuilder to 
     // their corresponding behaviors, "get" and "set" respectively. 
     property.SetGetMethod(currGetPropMthdBldr); 
     property.SetSetMethod(currSetPropMthdBldr); 

     return property; 

    } 

    public static object EditingObject(object obj) 
    { 
     // Create the typeBuilder 
     AssemblyName assembly = new AssemblyName("EditingWrapper"); 
     AppDomain appDomain = System.Threading.Thread.GetDomain(); 
     AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run); 
     ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assembly.Name); 

     // Create the class 
     TypeBuilder typeBuilder = moduleBuilder.DefineType("EditingWrapper", 
      TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | 
      TypeAttributes.BeforeFieldInit, typeof(System.Object)); 

     Type objType = obj.GetType(); 
     foreach (var propertyInfo in objType.GetProperties()) 
     { 
      string propertyName = propertyInfo.Name; 
      Type propertyType = propertyInfo.PropertyType; 

      // Create an automatic property 
      PropertyBuilder propertyBuilder = CreateAutomaticProperty(typeBuilder, propertyInfo); 

      // Set Range attribute 
      CreatePropertyAttribute(propertyBuilder, typeof(Category), new[]{"My new category value"}); 

     } 

     // Generate our type 
     Type generetedType = typeBuilder.CreateType(); 

     // Now we have our type. Let's create an instance from it: 
     object generetedObject = Activator.CreateInstance(generetedType); 

     return generetedObject; 
    } 
} 
0

Dưới đây là một "cheaty" cách để làm điều đó:

Nếu bạn có một số cố định các giá trị tiềm năng liên tục cho các tham số thuộc tính, bạn có thể xác định một tài sản riêng biệt cho mỗi giá trị tiềm năng của tham số (và cung cấp cho mỗi thuộc tính có thuộc tính hơi khác nhau), sau đó chuyển đổi thuộc tính mà bạn tham chiếu động.

Trong VB.NET, nó có thể trông như thế này:

Property Time As Date 

<Display(Name:="Month")> 
ReadOnly Property TimeMonthly As Date 
    Get 
     Return Time 
    End Get 
End Property 

<Display(Name:="Quarter")> 
ReadOnly Property TimeQuarterly As Date 
    Get 
     Return Time 
    End Get 
End Property 

<Display(Name:="Year")> 
ReadOnly Property TimeYearly As Date 
    Get 
     Return Time 
    End Get 
End Property 
Các vấn đề liên quan