Как открыть всплывающее окно WPF при нажатии другого элемента управления, используя только разметку XAML?
У меня есть два элемента управления TextBlock и всплывающее окно. Когда пользователь нажимает (MouseDown) на textblock, я хочу отобразить всплывающее окно. Я бы подумал, что могу сделать это с помощью 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, когда событие происходит на другом элементе управления?
5 ответов
Я сделал что-то просто, но это работает.
я использовал типичный ToggleButton, который я перезаписал как textblock, изменив его шаблон управления. Затем я просто привязал свойство 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>
следующий подход такой же, как у Хельге Кляйна, за исключением того, что всплывающее окно закрывается автоматически при нажатии в любом месте вне всплывающего окна (включая сам 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;
}
};
У меня были некоторые проблемы с 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>
как насчет:
<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
это referecing в Button
по имени и наоборот. Так что x:Name="..."
требуются как Popup
и Button
.
на самом деле это можно упростить, заменив Storyboard
материал с пользовательским SetProperty
описанное действие EventTrigger в ответ
другой способ сделать это:
<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>