简体   繁体   中英

Access XAML Control through C#

I have got some TextBlocks with x:Name , i'm able to access this control easily with C#. However I have also got some DoubleAnimations with an x:Name but unable to access these, why is this?

I have tried this but it does not work, I get an error saying Object reference not set to an instance of an object :

//Add completed methods
            DoubleAnimation da1 = (DoubleAnimation)Resources["storyboardLoad"];
            da1.Completed += DoubleAnimationCompleted;

Notifications.xaml

<Window x:Class="PhotoManagement.Views.Notification"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:PhotoManagement.Views"
        mc:Ignorable="d"
        Title="NotificationWindow" WindowStyle="None" AllowsTransparency="True" Background="Transparent" Width="350" SizeToContent="Height">
    <Window.Resources>
        <FontFamily x:Key="FontAwesome">/Fonts/#FontAwesome</FontFamily>
        <SolidColorBrush x:Key="colour1" Color="#29a1d5"/>
        <SolidColorBrush x:Key="colour2" Color="#248eb8"/>

        <Style TargetType="StackPanel">
            <Setter Property="Margin" Value="20" />
        </Style>
        <Style TargetType="TextBlock">
            <Setter Property="TextWrapping" Value="Wrap" />
            <Setter Property="Margin" Value="5" />
        </Style>
        <Style x:Key="NotificationIcon" TargetType="Border">
            <Setter Property="HorizontalAlignment" Value="Center" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="Background" Value="{DynamicResource colour2}" />
            <Style.Resources>
                <Style TargetType="TextBlock">
                    <Setter Property="FontFamily" Value="{StaticResource FontAwesome}" />
                    <Setter Property="Foreground" Value="White" />
                    <Setter Property="FontSize" Value="28" />
                    <Setter Property="Padding" Value="15" />
                </Style>
            </Style.Resources>
        </Style>
        <Style x:Key="NotificationText" TargetType="Border">
            <Setter Property="VerticalAlignment" Value="Stretch" />
            <Setter Property="HorizontalAlignment" Value="Stretch" />
            <Setter Property="Background" Value="{DynamicResource colour1}" />
            <Style.Resources>
                <Style TargetType="TextBlock">
                    <Setter Property="Foreground" Value="White" />
                    <Setter Property="VerticalAlignment" Value="Center" />
                    <Setter Property="HorizontalAlignment" Value="Center" />
                    <Setter Property="TextWrapping" Value="Wrap" />
                </Style>
            </Style.Resources>
        </Style>
        <Style x:Key="NotificationContainer" TargetType="Grid">
            <!-- Animation -->
            <Style.Triggers>
                <EventTrigger RoutedEvent="Window.Loaded">
                    <BeginStoryboard x:Name="StoryboardLoad">
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" 
                                             From="0.0"
                                             To="1.0"
                                             Duration="0:0:0.5"/>
                            <DoubleAnimation x:Name="storyboardLoad" Storyboard.TargetProperty="(UIElement.Opacity)" 
                                             From="1.0" 
                                             To="0.0" 
                                             Duration="0:0:1" 
                                             BeginTime="0:0:2" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>

                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                    <EventTrigger.Actions>
                        <RemoveStoryboard BeginStoryboardName="StoryboardLoad"/>
                        <RemoveStoryboard BeginStoryboardName="StoryboardFade"/>
                    </EventTrigger.Actions>
                </EventTrigger>

                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                    <BeginStoryboard x:Name="StoryboardFade">
                        <Storyboard>
                            <DoubleAnimation x:Name="storyboardFade" 
                                             Storyboard.TargetProperty="(UIElement.Opacity)" 
                                             From="1.0" 
                                             To="0.0" 
                                             Duration="0:0:1" 
                                             BeginTime="0:0:2" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid Style="{StaticResource NotificationContainer}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <Border Grid.Column="1"
                    CornerRadius="0 5 5 0"
                    Style="{StaticResource NotificationText}">
                <TextBlock Text="A request has been added!" 
                           x:Name="NotificationText"/>
            </Border>
            <Border Grid.Column="0" 
                    CornerRadius="5 0 0 5"
                    Style="{StaticResource NotificationIcon}">
                <TextBlock Text=""
                           x:Name="NotificationIcon"/>
            </Border>
        </Grid>
        <Grid.RenderTransform>
            <ScaleTransform ScaleY="1" />
        </Grid.RenderTransform>
    </Grid>
</Window>

Notifications.xaml.cs

using System;
using System.Windows;

namespace PhotoManagement.Views
{
    /// <summary>
    /// Interaction logic for Notification.xaml
    /// </summary>
    public partial class Notification : Window
    {
        public Notification() : base()
        {
            InitializeComponent();

            //Move other windows down when closed
            this.Closed += this.NotificationWindowClosed;

            //Get object properties
            this.NotificationText.Text = "Hello";
            this.NotificationIcon.Text = "G";
        }

        /// <summary>
        /// Show the Notification and setup it's content and actions
        /// </summary>
        public new void Show()
        {
            this.Topmost = true;
            this.Owner = System.Windows.Application.Current.MainWindow;
            this.Closed += this.NotificationWindowClosed;
            base.Show();

            //Add completed methods


            //Position the Notification
            var workingArea = SystemParameters.WorkArea;
            this.Left = (workingArea.Width - this.ActualWidth) / 2;
            double top = workingArea.Bottom - this.ActualHeight - 5;

            //Ensure new notifications are placed above older ones
            foreach (Window window in System.Windows.Application.Current.Windows)
            {
                string windowName = window.GetType().Name;

                if (windowName.Equals("Notification") && window != this)
                {
                    window.Topmost = true;
                    top = window.Top - window.ActualHeight - 5;
                }
            }

            this.Top = top;
        }
        private void ImageMouseUp(object sender,
        System.Windows.Input.MouseButtonEventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Close window once animation is complete
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void DoubleAnimationCompleted(object sender, EventArgs e)
        {
            if (!this.IsMouseOver)
            {
                this.Close();
            }
        }

        /// <summary>
        /// Move the next notification down once notification has closed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NotificationWindowClosed(object sender, EventArgs e)
        {
            foreach (Window window in System.Windows.Application.Current.Windows)
            {
                string windowName = window.GetType().Name;

                if (windowName.Equals("NotificationWindow") && window != this)
                {
                    // Adjust any windows that were above this one to drop down
                    if (window.Top < this.Top)
                    {
                        window.Top = window.Top + this.ActualHeight;
                    }
                }
            }
        }
    }
}

When you create a custom Window or UserControl , a (partial) class is generated for it that contains fields for any named child elements. That won't work for styles and templates however - likely because those can be applied to multiple controls, so a name doesn't necessarily map to a single item (or to an item at all).

The hard way

So the animation you're looking for is part of a style, which is a resource in your window. To be precise: your style has a collection of triggers. Its first trigger has a 'begin storyboard' action, and that action holds the storyboard you're interested in. It's going to take some searching and type-casting to get there:

Style style = (Style)Resources["NotificationContainer"];
EventTrigger trigger = (EventTrigger)style.Triggers[0];
BeginStoryboard beginStoryboard = (BeginStoryboard)trigger.Actions[0];
Storyboard storyboard = beginStoryboard.Storyboard;

As you can see, that's quite brittle. All you need to do to break it is reordering the event triggers in XAML...

The easy way

But why look up that storyboard anyway? Just bind its Completed event to an event handler in XAML directly:

<Storyboard Completed="WindowLoadedStoryboard_Completed">
    ...
</Storyboard>

Sadly, that's not going to work directly due to a "The event 'Completed' cannot be specified on a Target tag in a Style. Use an EventSetter instead." error. And an EventSetter is not going to work because Storyboard.Completed is not a routed event...

Fortunately, you can work around that by making the storyboard itself a resource, and referencing it in the begin-storyboard action:

<Resources>
    <Storyboard x:Key="WindowLoadedStoryboard" Completed="WindowLoadedStoryboard_Completed">
        ...
    </Storyboard>

    <Style ...>
        ...
        <BeginStoryboard Storyboard="{StaticResource WindowLoadedStoryboard}" />
        ...
    </Style>
</Resources>

Which, incidentally, would also make looking up that storyboard easier and more reliable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM