it-roy-ru.com

Как открыть всплывающее окно WPF при нажатии другого элемента управления, используя только разметку XAML?

У меня есть два элемента управления, TextBlock и всплывающее окно. Когда пользователь нажимает (MouseDown) на текстовом блоке, я хочу отобразить всплывающее окно. Я думаю, что я мог бы сделать это с помощью EventTrigger во всплывающем окне, но я не могу использовать сеттеры в EventTrigger, я могу только запускать раскадровки. Я хочу сделать это строго в XAML, потому что два элемента управления находятся в шаблоне, и я не знаю, как найти всплывающее окно в коде.

Это то, что концептуально я хочу сделать, но не могу, потому что вы не можете поместить установщик в EventTrigger (как вы можете с помощью DataTrigger):

<TextBlock x:Name="CCD">Some text</TextBlock>

<Popup>
    <Popup.Style>
        <Style>
            <Style.Triggers>
                <EventTrigger SourceName="CCD" RoutedEvent="MouseDown">
                    <Setter Property="Popup.IsOpen" Value="True" />
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Popup.Style>
...

Как лучше всего отображать всплывающее окно строго в XAML, когда событие происходит на другом элементе управления?

55
viggity

Я сделал что-то простое, но это работает.

Я использовал типичный ToggleButton, который я изменил как текстовый блок, изменив его шаблон управления. Затем я просто связал свойство IsChecked в ToggleButton со свойством IsOpen во всплывающем окне. У всплывающего окна есть некоторые свойства, такие как StaysOpen, которые позволяют изменять поведение при закрытии.

Следующие работы в XamlPad.

 <StackPanel>
  <ToggleButton Name="button"> 
    <ToggleButton.Template>
      <ControlTemplate TargetType="ToggleButton">
        <TextBlock>Click Me Here!!</TextBlock>
      </ControlTemplate>      
    </ToggleButton.Template>
  </ToggleButton>
  <Popup IsOpen="{Binding IsChecked, ElementName=button}" StaysOpen="False">
    <Border Background="LightYellow">
      <TextBlock>I'm the popup</TextBlock>
    </Border>
  </Popup> 
 </StackPanel>
80
John Melville

Следующий подход такой же, как у Хельге Кляйна, за исключением того, что всплывающее окно закрывается автоматически, когда вы щелкаете в любом месте за пределами всплывающего окна (включая сам ToggleButton):

<ToggleButton x:Name="Btn" IsHitTestVisible="{Binding ElementName=Popup, Path=IsOpen, Mode=OneWay, Converter={local:BoolInverter}}">
    <TextBlock Text="Click here for popup!"/>
</ToggleButton>

<Popup IsOpen="{Binding IsChecked, ElementName=Btn}" x:Name="Popup" StaysOpen="False">
    <Border BorderBrush="Black" BorderThickness="1" Background="LightYellow">
        <CheckBox Content="This is a popup"/>
    </Border>
</Popup>

«BoolInverter» используется в привязке IsHitTestVisible, поэтому при повторном нажатии кнопки «ToggleButton» всплывающее окно закрывается:

public class BoolInverter : MarkupExtension, IValueConverter
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool)
            return !(bool)value;
        return value;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Convert(value, targetType, parameter, culture);
    }
}

... которая показывает удобную технику объединения IValueConverter и MarkupExtension в одном.

Я обнаружил одну проблему с этой техникой: WPF глючит, когда на экране одновременно появляются два всплывающих окна. В частности, если ваша кнопка переключения находится в «всплывающем окне переполнения» на панели инструментов, то после нажатия на нее откроется два всплывающих окна. Затем вы можете обнаружить, что второе всплывающее окно (ваше всплывающее окно) останется открытым, когда вы нажмете где-нибудь еще в вашем окне. В этот момент закрыть всплывающее окно сложно. Пользователь не может щелкнуть ToggleButton снова, чтобы закрыть всплывающее окно, потому что IsHitTestVisible имеет значение false, потому что всплывающее окно открыто! В моем приложении мне пришлось использовать несколько хаков, чтобы смягчить эту проблему, например, следующий тест в главном окне, который говорит (голосом Луи Блэка): «если всплывающее окно открыто и пользователь щелкает где-то вне всплывающего окна, закройте чертово всплывающее окно. "

PreviewMouseDown += (s, e) =>
{
    if (Popup.IsOpen)
    {
        Point p = e.GetPosition(Popup.Child);
        if (!IsInRange(p.X, 0, ((FrameworkElement)Popup.Child).ActualWidth) ||
            !IsInRange(p.Y, 0, ((FrameworkElement)Popup.Child).ActualHeight))
            Popup.IsOpen = false;
    }
};
50
Qwertie

У меня были некоторые проблемы с частью MouseDown, но вот код, который может помочь вам начать.

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Control VerticalAlignment="Top">
            <Control.Template>
                <ControlTemplate>
                    <StackPanel>
                    <TextBox x:Name="MyText"></TextBox>
                    <Popup x:Name="Popup" PopupAnimation="Fade" VerticalAlignment="Top">
                        <Border Background="Red">
                            <TextBlock>Test Popup Content</TextBlock>
                        </Border>
                    </Popup>
                    </StackPanel>
                    <ControlTemplate.Triggers>
                        <EventTrigger RoutedEvent="UIElement.MouseEnter" SourceName="MyText">
                            <BeginStoryboard>
                                <Storyboard>
                                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup" Storyboard.TargetProperty="(Popup.IsOpen)">
                                        <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
                                    </BooleanAnimationUsingKeyFrames>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="UIElement.MouseLeave" SourceName="MyText">
                            <BeginStoryboard>
                                <Storyboard>
                                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup" Storyboard.TargetProperty="(Popup.IsOpen)">
                                        <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="False"/>
                                    </BooleanAnimationUsingKeyFrames>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Control.Template>
        </Control>
    </Grid>
</Window>
8
bendewey

Как насчет:

<Button x:Name="OpenPopup">Popup
    <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
            <EventTrigger.Actions>
                <BeginStoryboard>
                    <Storyboard>
                        <BooleanAnimationUsingKeyFrames 
                                 Storyboard.TargetName="ContextPopup" 
                                 Storyboard.TargetProperty="IsOpen">
                            <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
                        </BooleanAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger.Actions>
        </EventTrigger>
    </Button.Triggers>
</Button>
<Popup x:Name="ContextPopup"
       PlacementTarget="{Binding ElementName=OpenPopup}"
       StaysOpen="False">
    <Label>Popupcontent...</Label>
</Popup>

Обратите внимание, что Popup ссылается на Button по имени и наоборот. Таким образом, x:Name="..." требуется как для Popup, так и для Button.

На самом деле его можно еще больше упростить, заменив материал Storyboard настраиваемым действием SetProperty EventTrigger, описанным в этом SO ответе

8
BatteryBackupUnit

другой способ сделать это:

<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                    <StackPanel>
                        <Image Source="{Binding ProductImage,RelativeSource={RelativeSource TemplatedParent}}" Stretch="Fill" Width="65" Height="85"/>
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        <Button x:Name="myButton" Width="40" Height="10">
                            <Popup Width="100" Height="70" IsOpen="{Binding ElementName=myButton,Path=IsMouseOver, Mode=OneWay}">
                                <StackPanel Background="Yellow">
                                    <ItemsControl ItemsSource="{Binding Produkt.SubProducts}"/>
                                </StackPanel>
                            </Popup>
                        </Button>
                    </StackPanel>
                </Border>
0
Mike