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





У меня были некоторые проблемы с частью 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>
Следующий подход аналогичен подходу Хельге Кляйна, за исключением того, что всплывающее окно закрывается автоматически, когда вы щелкаете в любом месте за пределами всплывающего окна (включая сам 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 или щелчок в другом месте окна
StaysOpen = "false" является ключом к автоматическому закрытию вне компонентов. Почему, почему это значение по умолчанию - истина ??
@ChrisDolan Меня так раздражают такие вещи, как значения по умолчанию, которые противоречат практически всем известным мне принципам UX. Итак, я создаю неявный стиль для Popup и устанавливаю значения по умолчанию, которые мне нравятся, включая StaysOpen=False.
@Qwertie Это круто, но это привело к закрытию всплывающего окна, когда я взаимодействую с элементами во всплывающем окне. Я задал вопрос, если вы знаете, что я делаю не так.
Можем ли мы увидеть метод IsInRange ()?
Еще очень хотелось бы увидеть метод InRange. Это решение будет неполным без этого метода.
@ ashlar64 @Julien Извините, я пропустил ваши комментарии. Наконец-то добавил IsInRange
@Qwertie не уверен, почему он показывает мне, что пространство имен "локальное" не определено .. что я должен определить в локальном
@arihanthjain здесь, local - это пространство имен, которое содержит BoolInverter. По моему опыту, Visual Studio дает определение local по умолчанию всякий раз, когда вы создаете UserControl или Window.
другой способ сделать это:
<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. Вместо этого используйте Раскадровка.Цель.
см. также подход @Qwertie - более полезная версия, вдохновленная этим, которая автоматически закрывает всплывающее окно, когда вы нажимаете Alt-Tab или щелкаете за пределами всплывающего окна