Have to click away twice from Calendar in WPF
Asked Answered
G

9

24

Edit 2: Thank you all for your feedback. I solved the problem by adding this to my SelectedDatesChanged event:

Mouse.Capture(null);

When I select a date in my calendar, I want to click my "Go" button. However, I need to click the "Go" button twice: once to de-focus the calendar, and again to actually press it. The mouse leave event does not trigger on the calendar if an item is selected inside of it, and Keyboard.ClearFocus() does not de-focus it either.

Please, how can I get rid of the calendar's focus whenever I select a date? Thank you!

Edit: Clicking the "Go" button next was merely an example; if I want to select a textbox and I have just selected a date, I would also have to click twice to enter the textbox. The main issue is that, once the calendar is interacted with, it must be clicked out of once before interacting with any other elements.

Grimbly answered 17/8, 2014 at 19:40 Comment(2)
Do you want the mouse to be captured by Go button as soon as you select date in calendar?Fitted
Have you tried grouping the Calendar and Button in the same FocusScopeWoof
P
23

Releasing all mouse clicks in SelectedDatesChanged or GotMouseCapture will break the navigation between months on the calendar control. As pointed out in another answer, SelectedDatesChanged does not fire when the same date is selected.

So I used GotMouseCapture and only released the mouse focus when the clicked UIElement was a calendar day. It fixes the focus issue and doesn't break the rest of the control.

private void Calendar_GotMouseCapture(object sender, MouseEventArgs e)
{
    UIElement originalElement = e.OriginalSource as UIElement;
    if (originalElement is CalendarDayButton || originalElement is CalendarItem)
    {
        originalElement.ReleaseMouseCapture();
    }
}
Piliform answered 25/5, 2018 at 20:42 Comment(1)
This seems to be the best solution. There remains one problem though when choosing SelectionMode="MultipleRange": When selecting multiple dates by dragging the mouse only the first and last is highlighted until the mouse button is released. Looks strange.S
G
21

I solved the problem by adding this to my SelectedDatesChanged event:

Mouse.Capture(null);

Grimbly answered 18/8, 2014 at 2:31 Comment(1)
This breaks the month navigation buttons and doesn't work when the same date is selected. See my answer here.Piliform
F
8

If you select the same date then SelectedDatesChanged won't be raised and you will see the same issue where you need to click twice.

Ideally you should hook to GotMouseCapture event and release the mouse capture from original sender to avoid any mouse captures by calendar control.

private void calendar_GotMouseCapture(object sender, MouseEventArgs e)
{
    UIElement originalElement = e.OriginalSource as UIElement;
    if (originalElement != null)
    {
        originalElement.ReleaseMouseCapture();
    }
}

Note - You can extract out this in behavior as well by using attached property like mentioned in another answer.

Fitted answered 18/8, 2014 at 9:44 Comment(1)
This breaks the month navigation buttons and doesn't work when the same date is selected. See my answer here.Piliform
W
4

So it seems the Calendar captures the Mouse exclusively, One option could be to make a AttachedProperty to release the capture when the user clicks

Example:

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);
                }
            };
        }
    }
}

Now you can apply this AttachedProperty to your Calender and it will defocus once an item is selected.

Full Example:

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>

Code:

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);
                    }
                };
            }
        }
    }
}
Woof answered 17/8, 2014 at 22:12 Comment(1)
This is a very good option if you are using MVVM pattern. Thank so much.Eveland
S
1

I had the same issue weeks ago, you can use DatePicker which is a control that contains a Calendar, the calendar is displayed once the user clicks a button and when you select a date its automatically closed, the DatePicker Contains also a textbox when the date is visible, you can make it ReadOnly if you want: here a sample code for using 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>    

Hope this helps.

result :

result

Sanskrit answered 17/8, 2014 at 22:8 Comment(0)
S
1

Found this

Code:

public void ReleaseMouse()
{
    if (Mouse.Captured is CalendarItem) Mouse.Capture(null);
}

XAML:

<Calendar PreviewMouseUp="ReleaseMouse" />

Source: https://quick-geek.github.io/answers/819560/index.html

Easy and seems to have no drawbacks:

Spears answered 29/5, 2020 at 13:4 Comment(0)
S
1

Other answers had always some glitches with changing months and days. The only thing working for me I've found here

private void Calendar_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    if (Mouse.Captured is CalendarItem)
    {
        Mouse.Capture(null);
    }
}
Swanson answered 18/9, 2021 at 22:13 Comment(0)
G
1

I've added this directly in the .cs of the calendar page example (calendar.xaml.cs)

public Calendar()
{

InitializeComponent();

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

}
Grubby answered 8/6, 2022 at 13:45 Comment(0)
K
0

I handled SelectedDatesChanged event and I was displaying property OriginalSource of MouseButtonEventArgs. When you pick a date it displays Path, Rectangle and so on but when you pick for instance button or whatever beyond calendar it displays precisely System.Windows.Controls.Primitives.CalendarItem. Apparently calendar needs one more click in order to transfer mouse to another UIelement. My idea was to invoke event after first click on calendar so that it could lost capture right away.

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 });
    }
}

I created UIelement(in this case button) to invoke its MouseDown event. I had to add it to Panel(setting visibility did not work), otherwise event does not allow to invoke itself. Now when you click calendar it invokes tempButton.Click, it looses capture and when you press your Button "GO" it does not require clicking two times. I know it is a dirty way out, but working.

  <StackPanel>
        <Calendar local:CalendarM.RemoveProperty="True"/>
        <Button Content="Go" Click="but_Click"/>
        <TextBox Text="Text"/>
    </StackPanel>
</StackPanel>
Klepht answered 17/8, 2014 at 23:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.