locked
Create object from text in XAML RRS feed

  • 问题

  • Hi all,

    Consider the way you can declare Brush objects in XAML: you can write something like Background="Green", and the XAML parser will see the string "Green", see that Background is supposed to be a Brush, and somehow convert the string "Green" into (I assume) a new SolidColorBrush(Colors.Green).

    Can we use the same behaviour for custom objects? For example, suppose I have a UserControl that has a MyProperty of type MyClass, and MyClass can be built from a string. I would like to be able to write, in the UserControl, something like MyProperty="someText". If I do this now, I get an exception stating that a string cannot be converted into a MyClass.

    Is there any way for me to let the XAML parser know how to turn "someText" into a MyClass instance?

    2011年4月29日 9:37

答案

  • Hello Zappo, you need to implement a TypeConverter for the Polynomial class. For example, the following code works as you expect:

    Xaml markup:

    <UserControl x:Class="WpfApplication1.CustomUserControl"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           Name="Control">
      <ContentPresenter Content="{Binding Path=Polynomial, ElementName=Control}"/>
    </UserControl>
    
    

    Code behind:

    using System;
    using System.ComponentModel;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace WpfApplication1
    {
      public partial class CustomUserControl : UserControl
      {
        public static readonly DependencyProperty PolynomialProperty = DependencyProperty.Register("Polynomial", typeof(Polynomial), typeof(CustomUserControl));
        
        public CustomUserControl()
        {
          this.InitializeComponent();
        }
    
        public Polynomial Polynomial
        {
          get
          {
            return (Polynomial)this.GetValue(PolynomialProperty);
          }
    
          set
          {
            this.SetValue(PolynomialProperty, value);
          }
        }
      }
    
      internal class PolynomialTypeConverter : TypeConverter
      {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
          if (sourceType == typeof(string))
          {
            return true;
          }
          return false;
        }
    
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
          string text = value as string; 
          if (text != null)
          {
            return new Polynomial(text);
          }
          return null;
        }
    
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
          if (destinationType == typeof(string))
          {
            return true;
          }
          return false;
        }
    
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
          var polynomial = value as Polynomial;
          if (polynomial != null)
          {
            return polynomial.Text;
          }
          return null;
        }
      }
    
      [TypeConverter(typeof(PolynomialTypeConverter))]
      public class Polynomial
      {
        public Polynomial()
        {
        }
    
        public Polynomial(string text)
        {
          this.Text = text;
        }
    
        public string Text
        {
          get;
          set;
        }
      }
    }
    
    

    Hope this helps,

    Miguel.

    • 已建议为答案 Rex Honour 2011年4月30日 5:43
    • 已标记为答案 Min Zhu 2011年5月2日 2:08
    2011年4月30日 4:22
  • Hi Roozan,

    I'm not changing the question, the Color/Palette thing is just an example. It could be a polynomial, a set of coordinates, it doesn't matter - anything that has a string representation but is not itself a string. I apologize, but I'm unable to explain the issue any better. I'll use the polynomial example. Just assume there's a Polynomial class, and a MyUserControl that has one of those as a property. What I need is to be able to write this in a XAML file:

    <uc:MyUserControl MyPolynomial="3x^2+2x+5"/>
    
    My problem is not with how to parse a palette, or a polynomial, or whatever; I know how to do that. The problem is that the XAML engine will throw an exception on this line. It does so because it can't assign a string to a Polynomial. That's reasonable, except that cases exist where the XAML engine is indeed able to automatically convert strings into complex classes (e.g. brush), so I should be able to do the same.

    From looking around, I gather that the correct solution is to create a TypeConverter that can convert string to Polynomial, and mark the Polynomial class with a TypeConverterAttribute that refers to that TypeConverter; this should allow the XAML parser to know how to build the class from a string. It's kinda late now, I'll try it next time.

    Many thanks anyway.

    • 已标记为答案 Min Zhu 2011年5月2日 2:08
    2011年4月29日 17:11

全部回复

  • Hi Zappo,

    This might help you:

    <String xmlns=""clr-namespace:System;assembly=mscorlib"" >hello world</String> 

    Reading this value into your application involves three simple things.

    First, the application must reference presentationcore and presentationframework. This is a minor setback that I'm sure someone will rectify at some point.

    Secondly, now that we have access to XamlReader and XamlWriter (found in the namespace system.windows.markup), we can use the Load and Save methods to convert the XAML to the instance it represents or convert an instance to its XAML representation respectively. Finally, all that’s left is designing a new class to support all our configuration needs (try to make it serializable) and representing the instance of that class in XAML notation. So let's go ahead and create a simple project to consume this string configuration. To start with, create a simple console application, and add references to presentationcore.dll and presentationframework.dll. Once you are done with that, add the following lines of code to the Main Method :

    string xaml = @"<string assembly=""mscorlib"" >
      <String xmlns=""clr-namespace:System;assembly=mscorlib"" >
      hello world</String></string >";
    byte[] xaml_data = System.Text.Encoding.ASCII.GetBytes(xaml);
    System.IO.MemoryStream xaml_stream = new System.IO.MemoryStream(xaml_data);
    string xaml_object = (string)System.Windows.Markup.XamlReader.Load(xaml_stream);
    Console.WriteLine(xaml_object);

    Be sure to use the double quote notation if you are going to use a verbatim string like I have done. Running this code will produce the string hello world in the console window. There is one major thing to note here if you are not familiar with XAML as a whole. Just as in C# or any other .NET language where you must declare the namespaces you plan to use so that the compiler knows how to resolve the types you utilize in your listing, XAML, more specifically the xamlreader and xamlwriter classes, require you to specify a namespace to help resolve the elements you specify within the document . The attribute xmlns="clr-namespace:System;assembly=mscorlib" in this case specifies that the default namespace for the document will be the System namespace in the assembly mscorlib. So any type within that namespace (within the assembly) can be used here. Try out using different types within the namespace, changing namespaces and/or assemblies and also changing the values of the types you specify.

    Thanks,

     


    Roozan Parvez Bharucha MCT, MCITP (SQL Server 2008, Windows 7 Admin), MCTS (SQL Server 2008, Windows 7 Config, .NET Frmwk 3.5, .NET Frmwk 4.0 (Windows)), MCPD Enterprize App (Frmwk 3.5), MCPD Windows (Frmwk 4.0) CEO, RajAryanTech
    2011年4月29日 9:51
  • Hi Roozan,

    Thanks for your contribution, but I don't think it answers my question. I might have been insufficiently clear in explaining my problem; I'll try writing some sample code.

    Here is a class:

    public class MyClass
    {
      public MyClass()
      {
        //...perform default initialization
      }
    
      public MyClass(string initString)
      {
        //...perform initialization depending on the content of initString
      }
    }
    

    Here is a UserControl that has a property of type MyClass:

    public partial class MyUserControl : UserControl
    {
      //...MyUserControl initialization and stuff
    
      public MyClass MyProperty { get; set; }
    }
    

    Now, if I want to assign MyProperty, I need to do this:

    <local:MyUserControl x:Name="myucInstance"/>
    

    and then this in the codebehind:

    myucInstance.MyProperty = new MyClass("abcdef");
    
    What I would like to do, however, is this:

    <local:MyUserControl MyProperty="abcdef"/>
    
    This is nicer and cleaner. But it will not work; the XAML parser will complain that a string cannot be turned into a MyClass instance.

    2011年4月29日 13:15
  • Hi Zappo,
    As per your post I am now trying to focus on your point.  Here is it :
    The user control is defined by two partial classes. First one is generated based on XAML, the second one can be modified by developers manually. I can define custom attribute in second partial class. E.g.

    XAML:
    <UserControl x:Class="AdminTabs"> ... </UserControl>

    CS:
    [CustomAttributeTest("Test")]
    public partial class AdminTabs : System.Windows.Controls.UserControl { ... }
    I have made a simple sample based on your description. It works pretty well here. Based on my test, the designer will throw out an unhandled exception. But no error will be found while compiling and running. Below is the sample code.
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    
    namespace UCTest
    {
     public partial class TestUserControl : UserControl
     {
     private string numberFormat = "0";
     
     public string NumberFormat
     {
      set { numberFormat = value; }
     }
     public TestUserControl()
     {
      InitializeComponent();
      Loaded += new RoutedEventHandler(TestUserControl_Loaded);
      
     }
    
     void TestUserControl_Loaded(object sender, RoutedEventArgs e)
     {
      myTextBox.Text = numberFormat;
     }
     
     }
    }
    
    <Grid x:Name="LayoutRoot" Background="White">
     <strong> <uc:TestUserControl NumberFormat="0.0"></uc:TestUserControl>
    </strong> </Grid> 
    
     
    If cannot compile your solution, please remove the property first and compile it. After this, re-add the property and compile. If your problem persists there, please revert back....
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    If you are creating a DependencyProperty try this : (Rarely Required)
    <pre lang="x-c#">public static readonly DependencyProperty TextValueProperty = DependencyProperty.Register("TextValue", typeof(string), typeof(BlueText), new PropertyMetadata(OnTextValueChanged)); 
    
    public string TextValue 
    {
    get 
    { 
    return (string)GetValue(TextValueProperty); 
    }
    set 
    { 
    SetValue(TextValueProperty, value); 
    } 
    } 
    
    static void OnTextValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    {
    (d as BlueText).TextValueTB.Text = e.NewValue.ToString();
    }
    
    
     
    Regards,

    Roozan Parvez Bharucha MCT, MCITP (SQL Server 2008, Windows 7 Admin), MCTS (SQL Server 2008, Windows 7 Config, .NET Frmwk 3.5, .NET Frmwk 4.0 (Windows)), MCPD Enterprize App (Frmwk 3.5), MCPD Windows (Frmwk 4.0) CEO, RajAryanTech

    • 已建议为答案 Rex Honour 2011年4月29日 14:32
    2011年4月29日 14:25
  • Thanks again for your time Roozan, but I'm afraid this is still not the answer to my question. In the example you provide, the type of NumberFormat is string. That's a straightforward case. Handling string properties is easy, strings can be placed in XAML directly. My problem is with what happens the type of the property is a custom class. Look at the code I posted above, the type of MyProperty is not string. It's MyClass.

    For example, if you want to declare a Brush for the Background property of some control, you can write in XAML: Background="Green". And this will work, even though "Green" is a string and not a Brush, because the XAML parser knows how to turn a string into a Brush. I want to know how to get the same behaviour for my own classes.

    Suppose that, instead of a string called NumberFormat, you had a class called Palette that can be constructed from a string of the type "white,yellow,orange,red" (the constructor parses the string and builds the Palette instance appropriately). I want to know how to make it so that I could write, in XAML, <uc:TestUserControl Palette="white,yellow,orange,red"/> instead of being forced to instantiate Palette in the code-behind.

    After some searching, I think the answer lies in TypeConverter but I'm not 100% clear on how to use it yet.
    2011年4月29日 15:26
  • Hi Zappo,

    Your case is now changed than previous oneee.. jokes apart..

    The properties are made in CSharp file with .cs extension right ??

    Now in the constructor make a logic to seperate out your string as white, yellow, orange, red, etc and build a switch case condition.  Convert your types to appropriate output class you intend to by type casting and you should be ready to go... for example :

    you parsed the string as white and yellow then :

    In the property or constructor(parameterized) wherever, your logic says, just

    declare a variable of type Color say

    Color abc = new Color();

    set

    {

    if parameter="white"

     abcproperty = Color.White

    else

     abcproperty = Color.Yellow

    end if

    }

    hope it works now....

    Thanks


    Roozan Parvez Bharucha MCT, MCITP (SQL Server 2008, Windows 7 Admin), MCTS (SQL Server 2008, Windows 7 Config, .NET Frmwk 3.5, .NET Frmwk 4.0 (Windows)), MCPD Enterprize App (Frmwk 3.5), MCPD Windows (Frmwk 4.0) CEO, RajAryanTech
    • 已建议为答案 Rex Honour 2011年4月29日 15:38
    2011年4月29日 15:38
  • Hi Roozan,

    I'm not changing the question, the Color/Palette thing is just an example. It could be a polynomial, a set of coordinates, it doesn't matter - anything that has a string representation but is not itself a string. I apologize, but I'm unable to explain the issue any better. I'll use the polynomial example. Just assume there's a Polynomial class, and a MyUserControl that has one of those as a property. What I need is to be able to write this in a XAML file:

    <uc:MyUserControl MyPolynomial="3x^2+2x+5"/>
    
    My problem is not with how to parse a palette, or a polynomial, or whatever; I know how to do that. The problem is that the XAML engine will throw an exception on this line. It does so because it can't assign a string to a Polynomial. That's reasonable, except that cases exist where the XAML engine is indeed able to automatically convert strings into complex classes (e.g. brush), so I should be able to do the same.

    From looking around, I gather that the correct solution is to create a TypeConverter that can convert string to Polynomial, and mark the Polynomial class with a TypeConverterAttribute that refers to that TypeConverter; this should allow the XAML parser to know how to build the class from a string. It's kinda late now, I'll try it next time.

    Many thanks anyway.

    • 已标记为答案 Min Zhu 2011年5月2日 2:08
    2011年4月29日 17:11
  • Hi Zappo,

    Nothing is late and impossible my friend... Go ahead and good luck man... But if you think my solutions provided even a bit to your way and eased them, please dont forget to mark it as answer dear...

    Regards,


    Roozan Parvez Bharucha MCT, MCITP (SQL Server 2008, Windows 7 Admin), MCTS (SQL Server 2008, Windows 7 Config, .NET Frmwk 3.5, .NET Frmwk 4.0 (Windows)), MCPD Enterprize App (Frmwk 3.5), MCPD Windows (Frmwk 4.0) CEO, RajAryanTech
    2011年4月29日 17:20
  • Hello Zappo, you need to implement a TypeConverter for the Polynomial class. For example, the following code works as you expect:

    Xaml markup:

    <UserControl x:Class="WpfApplication1.CustomUserControl"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           Name="Control">
      <ContentPresenter Content="{Binding Path=Polynomial, ElementName=Control}"/>
    </UserControl>
    
    

    Code behind:

    using System;
    using System.ComponentModel;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace WpfApplication1
    {
      public partial class CustomUserControl : UserControl
      {
        public static readonly DependencyProperty PolynomialProperty = DependencyProperty.Register("Polynomial", typeof(Polynomial), typeof(CustomUserControl));
        
        public CustomUserControl()
        {
          this.InitializeComponent();
        }
    
        public Polynomial Polynomial
        {
          get
          {
            return (Polynomial)this.GetValue(PolynomialProperty);
          }
    
          set
          {
            this.SetValue(PolynomialProperty, value);
          }
        }
      }
    
      internal class PolynomialTypeConverter : TypeConverter
      {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
          if (sourceType == typeof(string))
          {
            return true;
          }
          return false;
        }
    
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
          string text = value as string; 
          if (text != null)
          {
            return new Polynomial(text);
          }
          return null;
        }
    
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
          if (destinationType == typeof(string))
          {
            return true;
          }
          return false;
        }
    
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
          var polynomial = value as Polynomial;
          if (polynomial != null)
          {
            return polynomial.Text;
          }
          return null;
        }
      }
    
      [TypeConverter(typeof(PolynomialTypeConverter))]
      public class Polynomial
      {
        public Polynomial()
        {
        }
    
        public Polynomial(string text)
        {
          this.Text = text;
        }
    
        public string Text
        {
          get;
          set;
        }
      }
    }
    
    

    Hope this helps,

    Miguel.

    • 已建议为答案 Rex Honour 2011年4月30日 5:43
    • 已标记为答案 Min Zhu 2011年5月2日 2:08
    2011年4月30日 4:22
  • Hi Zappo1980,

    I’m glad to hear that you have resolved this problem. I also think the TypeConverter is the answer to your question.

    I have marked the corresponding replies as answers in order to highlight them, for the benifits of other community members.

    Thanks all for the helpful suggestions.

    Best regards,


    Min Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    2011年5月2日 2:19