Как открыть всплывающее окно 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, который я рестайлинг в качестве текстового блока, изменив его шаблон управления. Затем я просто привязал свойство 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>
следующий подход такой же, как у Helge Klein, за исключением того, что всплывающее окно автоматически закрывается при нажатии в любом месте вне всплывающего окна (включая сам 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
ссылается на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>