在Silverlight中获取VisualState

时间:2011-08-09 10:24:31

标签: c# wpf silverlight

我正在尝试创建自己的验证文本框,并使用VisualStateManager来更改字段的状态。如果我能读取文本框的当前状态将非常有用。我找不到怎么做。任何人都知道如何获取元素的当前VisualState(TextBox)?

3 个答案:

答案 0 :(得分:3)

VisualStateManager不公开获取可视状态的机制。 Web上有解决方案,例如this blog post描述了如何扩展VSM以使当前状态变得可用。

答案 1 :(得分:2)

科林是对的,但如果你不介意有点厚颜无耻,那就可以做到。

创建一个新的Silverlight应用程序。在MainPage中包含以下Xaml: -

<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>

现在从此博客获取VisualTreeEnumeration扩展方法类的代码:Visual Tree Enumeration并将其包含在应用程序中。

现在将此代码包含在MainPage代码隐藏中: -

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();    
    }
}

运行此选项并注意列出的可视状态跟踪文本框的鼠标悬停和聚焦状态。

该代码挖掘出控件的第一个可视子元素,其中VisualStateGroups属性将被附加到其中。然后,它会列出列出其名称的每个VisualStateGroup,然后检查该组的CurrentState属性,以确定是否已选择状态。

答案 2 :(得分:1)

实际上,VisualStateManager确实支持这样做的机制。我自己非常需要这个问题并最终得到了这个问题和答案。这些解决方案都没有吸引我,所以我去寻找自己的编码。以下是代码,享受!

首先,我们需要一个非常简单的控制。让我们使用历史悠久的定制按钮控件(是的,无聊,我知道)。

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;
        }
    }
}

现在,让我们假装你有一个控件模板,特别是你正在开发的这个新的MyCustomButton控件,并且在开发过程中,你正在尝试调试你的VSM逻辑。通过在控制中控制此代码,您可以绑定到CurrentCommonVisualState属性:

<!-- 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>

就是这样,现在您可以确定VisualState CommonStates中当前VisualStateGroup的内容。每个VisualStateGroup您需要一个属性,您有兴趣在开发过程中“观察”。

对于我自己的自定义控件开发,我只是在控制代码的底部放置了一个#region Temp Testing Code区域语句。这样,我可以将所有这些代码保存在一个地方,并且在我完成测试时可以批量删除它(嗯,更好的是,我可以有条件地编译出来)。

无论如何,希望这有助于其他人。