WPF中的日历控件需要点击两次才能关闭。

24

编辑2:感谢大家的反馈。我通过在SelectedDatesChanged事件中添加以下内容解决了这个问题:

Mouse.Capture(null);

当我在日历中选择一个日期后,我想要点击我的“Go”按钮。然而,我需要点击两次“Go”按钮:一次取消日历的焦点,再次才能实际按下它。如果在日历中选择了项目,则鼠标离开事件不会触发,并且Keyboard.ClearFocus()也无法取消其焦点。

请问,我如何在选择日期时取消日历的焦点? 谢谢!

编辑:点击旁边的“Go”按钮只是一个例子;如果我想要选择一个文本框,并且我刚刚选择了一个日期,我也必须点击两次才能进入文本框。主要问题是,一旦与日历交互,必须先点击一次以取消任何其他元素的交互。


你想在选择日历日期后立即通过Go按钮捕获鼠标吗? - Rohit Vats
你尝试过将“日历”和“按钮”分组到同一个“FocusScope”中吗? - sa_ddam213
9个回答

22

SelectedDatesChangedGotMouseCapture事件中释放所有鼠标点击会破坏日历控件之间的月份导航。正如另一个答案中指出的那样,当选择相同日期时,SelectedDatesChanged不会触发。

因此,我使用了GotMouseCapture,并且只在单击的UI元素为日历日期时才释放鼠标焦点。这修复了焦点问题,并且不会破坏控件的其余部分。

private void Calendar_GotMouseCapture(object sender, MouseEventArgs e)
{
    UIElement originalElement = e.OriginalSource as UIElement;
    if (originalElement is CalendarDayButton || originalElement is CalendarItem)
    {
        originalElement.ReleaseMouseCapture();
    }
}

这似乎是最好的解决方案。然而,当选择SelectionMode="MultipleRange"时,仍然存在一个问题:当通过拖动鼠标选择多个日期时,只有第一个和最后一个日期会被突出显示,直到释放鼠标按钮。看起来很奇怪。 - A Person

21

我通过在SelectedDatesChanged事件中添加以下代码解决了这个问题:

Mouse.Capture(null);


3
这会破坏月导航按钮,并且在选择相同日期时也无法工作。请查看我的答案在这里 - Millie Smith

8
如果您选择相同的日期,则不会引发SelectedDatesChanged事件,您将看到需要点击两次才能选中日期的问题。
理想情况下,您应该挂钩GotMouseCapture事件并从原始发送者释放鼠标捕获,以避免日历控件进行任何鼠标捕获。
private void calendar_GotMouseCapture(object sender, MouseEventArgs e)
{
    UIElement originalElement = e.OriginalSource as UIElement;
    if (originalElement != null)
    {
        originalElement.ReleaseMouseCapture();
    }
}

注意 - 您也可以使用附加属性来提取此行为,如另一个答案中所述。

1
这会破坏月份导航按钮,并且在选择相同日期时无法工作。请查看我的答案此处 - Millie Smith

4

看起来 Calendar 独占了鼠标,一种解决方法是创建一个 AttachedProperty ,在用户单击时释放捕获。

示例:

public static class CalandarHelper 
{
    public static readonly DependencyProperty SingleClickDefocusProperty =
        DependencyProperty.RegisterAttached("SingleClickDefocus", typeof(bool), typeof(Calendar)
        , new FrameworkPropertyMetadata(false, new PropertyChangedCallback(SingleClickDefocusChanged)));

    public static bool GetSingleClickDefocus(DependencyObject obj) {
        return (bool)obj.GetValue(SingleClickDefocusProperty);
    }

    public static void SetSingleClickDefocus(DependencyObject obj, bool value) {
        obj.SetValue(SingleClickDefocusProperty, value);
    }

    private static void SingleClickDefocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Calendar) 
        {
            Calendar calendar = d as Calendar;
            calendar.PreviewMouseDown += (a, b) =>
            {
                if (Mouse.Captured is Calendar || Mouse.Captured is System.Windows.Controls.Primitives.CalendarItem)
                {
                    Mouse.Capture(null);
                }
            };
        }
    }
}

现在您可以将这个 AttachedProperty 应用到您的 Calender 上,它会在选择项目后自动解除焦点。 完整示例: Xaml:
<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:helpers="clr-namespace:WpfApplication2"
        Title="MainWindow" Width="300" >

    <StackPanel>
        <Calendar helpers:CalandarHelper.SingleClickDefocus="True" />
        <TextBox />
    </StackPanel>
</Window>

代码:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication2 
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window 
    {
        public MainWindow() 
        {
            InitializeComponent();
        }
    }

    public static class CalandarHelper 
    {
        public static readonly DependencyProperty SingleClickDefocusProperty =
            DependencyProperty.RegisterAttached("SingleClickDefocus", typeof(bool), typeof(Calendar)
            , new FrameworkPropertyMetadata(false, new PropertyChangedCallback(SingleClickDefocusChanged)));

        public static bool GetSingleClickDefocus(DependencyObject obj) {
            return (bool)obj.GetValue(SingleClickDefocusProperty);
        }

        public static void SetSingleClickDefocus(DependencyObject obj, bool value) {
            obj.SetValue(SingleClickDefocusProperty, value);
        }

        private static void SingleClickDefocusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is Calendar) 
            {
                Calendar calendar = d as Calendar;
                calendar.PreviewMouseDown += (a, b) =>
                {
                    if (Mouse.Captured is Calendar || Mouse.Captured is System.Windows.Controls.Primitives.CalendarItem)
                    {
                        Mouse.Capture(null);
                    }
                };
            }
        }
    }
}

如果你正在使用MVVM模式,这是一个非常好的选择。非常感谢。 - Álvaro García

1

其他答案在更改月份和日期上总会有一些问题。我发现唯一可行的方法是在这里找到。

private void Calendar_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    if (Mouse.Captured is CalendarItem)
    {
        Mouse.Capture(null);
    }
}

1

几周前我遇到了同样的问题,您可以使用DatePicker,它是一个包含日历的控件,当用户单击按钮时,日历会显示出来,当您选择日期后,它会自动关闭,DatePicker还包含一个文本框,当日期可见时,您可以将其设置为只读:以下是使用DatePicker的示例代码:

<DatePicker Name="TestDatePicker" Width="120" Height="25" >
        <DatePicker.Resources>
            <Style TargetType="DatePickerTextBox">
                <Setter Property="IsReadOnly" Value="True"></Setter>
                <Setter Property="Text" Value="Select a Date"></Setter>
            </Style>
        </DatePicker.Resources>
</DatePicker>    

希望这可以帮助到你。

result :

result


1

0

我已经直接在日历页面的 .cs 文件中添加了这个内容,例如(calendar.xaml.cs)

public Calendar()
{

InitializeComponent();

this.PreviewMouseUp += (s, e) => { if (Mouse.Captured is CalendarItem) Mouse.Capture(null); };

}

0
我处理了SelectedDatesChanged事件,并显示MouseButtonEventArgs的OriginalSource属性。当您选择日期时,它会显示Path、Rectangle等内容,但是当您选择日历之外的按钮或其他内容时,它会精确地显示System.Windows.Controls.Primitives.CalendarItem。显然,日历需要再点击一次才能将鼠标传递给另一个UI元素。我的想法是在第一次单击日历后调用事件,以便它可以立即失去捕获。
public static class CalendarM
{
    private static Button tempButton;
    public static bool GetRemoveProperty(DependencyObject obj)
    {
        return (bool)obj.GetValue(RemoveFocusProperty);
    }

    public static void SetRemoveProperty(DependencyObject obj, bool value)
    {
        obj.SetValue(RemoveFocusProperty, value);
    }
    public static readonly DependencyProperty RemoveFocusProperty = DependencyProperty.RegisterAttached("RemoveFocus", typeof(bool), typeof(CalendarM),
        new FrameworkPropertyMetadata(new PropertyChangedCallback((x, y) =>
        {
            if (x is Calendar && GetRemoveProperty((DependencyObject)x))
            {
                tempButton = new Button() { Width = 0, Height = 0 };
                ((System.Windows.Controls.Panel)((FrameworkElement)x).Parent).Children.Add(tempButton);
                tempButton.Click += (s1, s2) =>
                {
                };
                ((Calendar)x).SelectedDatesChanged += CalendarM_SelectedDatesChanged;
            }
        })));
    static void CalendarM_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
    {
        tempButton.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = Button.MouseDownEvent });
    }
}

我创建了UI元素(在这种情况下是按钮),以调用其MouseDown事件。我不得不将它添加到Panel中(设置可见性无效),否则事件不允许调用自身。现在,当您单击日历时,它会调用tempButton.Click,失去捕获,并且当您按下“GO”按钮时,它不需要点击两次。我知道这是一种不太好的解决方法,但它能够工作。
  <StackPanel>
        <Calendar local:CalendarM.RemoveProperty="True"/>
        <Button Content="Go" Click="but_Click"/>
        <TextBox Text="Text"/>
    </StackPanel>
</StackPanel>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接