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

У меня есть два элемента управления: TextBlock и PopUp. Когда пользователь нажимает (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, когда событие происходит в другом элементе управления?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
63
0
139 990
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

У меня были некоторые проблемы с частью 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>
Ответ принят как подходящий

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

Я использовал типичный ToggleButton, который я переделал в текстовый блок, изменив его шаблон управления. Затем я просто привязал свойство IsChecked в ToggleButton к свойству IsOpen во всплывающем окне. Popup имеет некоторые свойства, такие как 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>

см. также подход @Qwertie - более полезная версия, вдохновленная этим, которая автоматически закрывает всплывающее окно, когда вы нажимаете Alt-Tab или щелкаете за пределами всплывающего окна

Simon_Weaver 30.01.2012 06:16

Следующий подход аналогичен подходу Хельге Кляйна, за исключением того, что всплывающее окно закрывается автоматически, когда вы щелкаете в любом месте за пределами всплывающего окна (включая сам 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;
    }
};
// Elsewhere...
public static bool IsInRange(int num, int lo, int hi) => 
    num >= lo && num <= hi;

это отлично работает, включая переход к другому приложению с помощью alt-tab или щелчок в другом месте окна

Simon_Weaver 30.01.2012 06:03

StaysOpen = "false" является ключом к автоматическому закрытию вне компонентов. Почему, почему это значение по умолчанию - истина ??

Chris Dolan 16.10.2012 20:08

@ChrisDolan Меня так раздражают такие вещи, как значения по умолчанию, которые противоречат практически всем известным мне принципам UX. Итак, я создаю неявный стиль для Popup и устанавливаю значения по умолчанию, которые мне нравятся, включая StaysOpen=False.

erodewald 27.04.2013 00:16

@Qwertie Это круто, но это привело к закрытию всплывающего окна, когда я взаимодействую с элементами во всплывающем окне. Я задал вопрос, если вы знаете, что я делаю не так.

erodewald 27.04.2013 01:02

Можем ли мы увидеть метод IsInRange ()?

Julien 21.06.2013 01:04

Еще очень хотелось бы увидеть метод InRange. Это решение будет неполным без этого метода.

ashlar64 30.04.2015 17:00

@ ashlar64 @Julien Извините, я пропустил ваши комментарии. Наконец-то добавил IsInRange

Qwertie 10.05.2019 06:05

@Qwertie не уверен, почему он показывает мне, что пространство имен "локальное" не определено .. что я должен определить в локальном

arihanth jain 30.10.2019 14:30

@arihanthjain здесь, local - это пространство имен, которое содержит BoolInverter. По моему опыту, Visual Studio дает определение local по умолчанию всякий раз, когда вы создаете UserControl или Window.

Qwertie 01.11.2019 04:41

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

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

Ниже для демонстрации EventTrigger используется Popup. Это означает, что нам не нужен ToggleButton для привязки состояния. В этом примере используется событие Click для Button. Вы можете адаптировать его для использования другой комбинации элемента / события.

<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 Ответе

в случае, если кто-то захочет использовать это в шаблоне, например, DataGridTemplateColumn, Storyboard.TargetName не сможет найти Popup. Вместо этого используйте Раскадровка.Цель.

Steffen Winkler 25.07.2019 11:14

Другие вопросы по теме