开发者

Get the VisualState in Silverlight

开发者 https://www.devze.com 2023-03-26 17:51 出处:网络
I am trying to make my own validation textbox and uses the VisualStateManager to change the states of the field. It would be very helpful if I can read the current state of the textbox. I can\'t find

I am trying to make my own validation textbox and uses the VisualStateManager to change the states of the field. It would be very helpful if I can read the current state of the textbox. I can't find how to do this. Anyone know how to开发者_如何学C get the hold of the current VisualState of an element (TextBox)?


The VisualStateManager does not expose a mechanism for getting the visual state. There are solution on the web, for example this blog post describes how to extend the VSM to make the current state get-able.


Colin is right but it can be done if you don't mind being a little cheeky.

Create a new Silverlight application. Include the following Xaml in the MainPage:-

<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="200" />
    </Grid.RowDefinitions>
    <TextBox x:Name="txt" Grid.Row="0"/>
    <ListBox x:Name="result" Grid.Row="1" />
</Grid>

Now get the code for the VisualTreeEnumeration extension methods class from this blog: Visual Tree Enumeration and include it in the application.

Now include this code in the MainPage code-behind:-

public partial class MainPage: UserControl
{
    DispatcherTimer timer = new DispatcherTimer();

    public MainPage()
    {
        InitializeComponent();
        Loaded += new RoutedEventHandler(MainPage_Loaded);
        Unloaded += new RoutedEventHandler(MainPage_Unloaded);
    }

    void MainPage_Unloaded(object sender, RoutedEventArgs e)
    {
        timer.Stop();
    }

    void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        timer.Interval = TimeSpan.FromMilliseconds(500);
        timer.Tick += (s, args) =>
        {
            FrameworkElement innerControl = txt.Descendents(1).First() as FrameworkElement;

            result.ItemsSource = VisualStateManager.GetVisualStateGroups(innerControl).OfType<VisualStateGroup>()
                .Select(vsg => vsg.Name + " : " + (vsg.CurrentState != null ? vsg.CurrentState.Name : "<none>"));
        };
        timer.Start();    
    }
}

Run this and notice that the visual states listed track mouse over and focused states of the text box.

The code digs out the first visual child of the control which where the VisualStateGroups property would be attached. It then enumerates each VisualStateGroup listing its name and then examines the CurrentState property of that group to determine if a state has been selected.


Actually, the VisualStateManager does support a mechanism for doing this. I had this very need myself and ended up at this SO Q&A. None of these solutions appealed to me, so I went looking and coded my own. Here is the code below, enjoy!

First, we need a very simple control. Let's use the time-honored customized button control (yeah, boring, I know).

public class MyCustomButton : System.Windows.Controls.Button
{
    static MyCustomButton()
    {
        FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(
            typeof(MyCustomButton),
            new FrameworkPropertyMetadata(typeof(MyCustomButton)));
    }

    public MyCustomButton()
        : base()
    {
    }

    #region CurrentCommonVisualState Property

    private static readonly DependencyPropertyKey CurrentCommonVisualStatePropertyKey =
        DependencyProperty.RegisterReadOnly(
            "CurrentCommonVisualState",
            typeof(string),
            typeof(MyCustomButton));

    public static readonly DependencyProperty CurrentCommonVisualStateProperty =
        MyCustomButton.CurrentCommonVisualStatePropertyKey.DependencyProperty;

    [Category("Miscellaneous")]
    [Bindable(true)]
    [ReadOnly(true)]
    public string CurrentcommonVisualState
    {
        get { return (string)base.GetValue(CurrentCommonVisualStateProperty); }
        protected set { base.SetValue(CurrentCommonVisualStatePropertyKey, value); }
    }

    #endregion CurrentCommonVisualState Property

    #region VisualStateManager Methods

    protected T GetTemplateChild<T>(string name) where T : DependencyObject
    {
        return GetTemplateChild(name) as T;
    }

    // In WPF, in order to use the VSM, the VSM must be the first child of
    // your root control template element and that element must be derived
    // from System.Windows.Controls.Panel (e.g., like a Grid control).
    // 
    // This restriction no longer exists with Windows Store apps.
    //
    // But this is why the first parameter to this method is of type
    // Panel.
    protected VisualStateGroup GetVisualStateGroup(Panel visualStateManagerElement,
        string visualStateGroupName)
    {
        if (visualStateManagerElement == null)
        {
            return null;
        }

        VisualStateGroup result = null;
        var visualStateGroups = 
            VisualStateManager.GetVisualStateGroups(visualStateManagerElement);

        foreach (VisualStateGroup vsg in visualStateGroups)
        {
            if (vsg.Name == visualStateGroupName)
            {
                result = vsg;
                break;
            }
        }

        return result;
    }

    // When the control changes visual state, get the name of the
    // current visual state from the CommonStates visual state group
    // and set the CurrentCommonVisualState property.
    //
    // Then, you could potentially bind to that property.
    internal override void ChangeVisualState(bool useTransitions)
    {
        // Using IL Spy, look at PresentationFramework.dll and see how
        // MS implements this method. We're going to add some
        // functionality here to get the current visual state.
        base.ChangeVisualStat(useTransitions);

        Panel templateRoot = this.GetTemplateChild<Panel>("TemplateRoot");

        VisualStateGroup vsg = this.GetVisualStateGroup(templateRoot, "CommonStates");
        if (vsg != null && vsg.CurrentState != null)
        {
            this.CurrentCommonVisualState = vsg.CurrentState.Name;
        }
    }
}

Now, let's pretend you have a control template especially for this new MyCustomButton control that you're developing, and, during development, you're trying to debug your VSM logic. By having this code in your control, in your test application, you can bind to the CurrentCommonVisualState property:

<!-- Imagine you have some other WPF XAML markup above for your MainWindow test application -->
<Grid>
  <Grid.RowDefinitions>
     <RowDefinition Height="Auto" />
     <RowDefinition Height="Auto" />
  </Grid>
  <MyCustomButton x:Name="MyCustomButton" 
                  Grid.Row="0" 
                  Width="75", 
                  Height="23" 
                  Content="My Button" />
  <TextBox x:Name="TestCurrentCommonVisualStateName"
           Grid.Row="1"
           Width="100"
           Height="20"
           Text="{Binding CurrentCommonVisualState, Mode=OneWay, ElementName=MyCustomButton}" />
</Grid>

That's it, now you can determine what the current VisualState is out of the CommonStates VisualStateGroup. You would need one property per VisualStateGroup you are interested in "watching" during development.

For my own custom control development, I simply placed a #region Temp Testing Code region statement at the bottom of my control code. This way, I can keep all this code in one place and can wholesale delete it when I'm done testing (hmm, better yet, I could conditionally compile it out).

Anyway, hope this helps someone else.

0

精彩评论

暂无评论...
验证码 换一张
取 消