nelaed
nelaed

Reputation: 187

Detecting click on and outside of PopupButton in WPF

I have a problem with PopupButton in WPF. When I press it once the popup appears with some items to select (ElementTreeControl). After i press the same PopupButton it should close - yet it closes and opens again. I solved that and it was working but when i click outside of this control and it closes (StayOpen = false) I have problem with reopening it again - need to press PopupButton two times.

Is there some property or workaround on how to detect when the control was pressed and when it was area outside of it?

I want the PopupButton to be:

When closed:

When open:

Popup button click action:

private Popup rtWindowBoundsPopup;

private async void ButtonClickAsync(object pmSender, RoutedEventArgs pmE)
{
    if (pmSender is PopupButton lcButton)
    {
        if (lcButton.Tag is StarElement lcStarElement)
        {
            if (!string.IsNullOrEmpty(DatabaseName))
            {
                lcStarElement = await rtStarHelper.AssureMetaData(lcStarElement, DatabaseName);
            }
            else
            {
                StarDim lcStarDim = rtStarHelper.GetDimFromDimId(lcStarElement.Dim.DimId, true);
                lcStarElement.Dim = await rtStarHelper.AssureMetaData(lcStarDim);
            }
            ShowTreeViewPopup(lcButton, lcStarElement);
        }
    }
}


private void ShowTreeViewPopup(PopupButton pmButton, StarElement pmStarElement)
 {
     ElementTreeControl lcElementTreeControl;
     if (rtWindowBoundsPopup == null)
     {
         rtWindowBoundsPopup = new Popup();// { IsLightDismissEnabled = true };
        rtWindowBoundsPopup.Opened += WindowBoundsPopupOpened;
     }

     if (rtWindowBoundsPopup.Child is ElementTreeControl lcTreeControl)
     {
         lcElementTreeControl = lcTreeControl;
         lcElementTreeControl.HideAddionalCols();
     }
     else
     {
         lcElementTreeControl = new ElementTreeControl { Tag = pmButton };
         rtWindowBoundsPopup.Child = lcElementTreeControl;
         lcElementTreeControl.SelectionChanged += PopupListBoxSelectionChangedAsync;
     }

     Point lcPoint = UiHelper.CalcOffsets(pmButton);
     Rect lcCurrentwindowbounds = CurrentWindow.RestoreBounds;
     if (lcPoint.Y < lcCurrentwindowbounds.Height / 2)
     {
         lcElementTreeControl.MaxHeight = lcCurrentwindowbounds.Height - lcPoint.Y - pmButton.ActualHeight;
     }
     else
     {
         lcElementTreeControl.MaxHeight = lcPoint.Y - pmButton.ActualHeight;
     }
     lcElementTreeControl.Width = Math.Max(pmButton.ActualWidth, 400);
     lcElementTreeControl.MaxWidth = lcCurrentwindowbounds.Width;
     lcElementTreeControl.MinHeight = 150;
     lcElementTreeControl.Init(rtStarCube, pmStarElement, rtStarHelper);
     lcElementTreeControl.CaptionColWidth = lcElementTreeControl.Width;
     rtWindowBoundsPopup.PlacementTarget = pmButton;
     rtWindowBoundsPopup.Placement = PlacementMode.Bottom;
     rtWindowBoundsPopup.StaysOpen = false;//false;
     rtWindowBoundsPopup.Closed -= WindowBoundsPopupOnClosed;
     rtWindowBoundsPopup.Closed += WindowBoundsPopupOnClosed;

     rtWindowBoundsPopup.IsOpen = true;
}

In WindowBoundsPopupOnClosed nothing happens, i have tried to make it work there but didn't menage to do so.

Upvotes: 0

Views: 252

Answers (1)

BionicCode
BionicCode

Reputation: 28968

Where do you actually close the Popup? I only see you setting IsOpen to true. Current behavior: the first click on PopupButton will open the Popup. Now because StaysOpen is set to false, clicking the button (which is outside the Popup) a second time will close the Popup, as the popup has lost focus, since it is moved from Popup to the PopupButton. IsOpen returns now false. This second click then invokes the event handler ButtonClickAsync which sets IsOpen back to true again, which reopens the Popup.

Your code is too complicated, because you are using C# instead of XAML.

The PopupButton should be a ToggleButton or based on it.

<Window>
  <StackPanel>
    <ToggleButton x:Name="PopupButton" />
    <Popup IsOpen="{Binding ElementName=PopupButton, Path=IsChecked}">
      <ElementTreeControl Tag="pmButton" />
    </Popup>
  </StackPanel>
</Window>

Using EventTrigger

An alternative approach is to use EventTrigger. This can be easier in situations, where you don't have access to the triggering control 'e.g., the PopupButton, as it might be defined out of scope e.g inside some other template. This example still assumes that PopupButton is derived from ToggleButton:

The Window, which hosts the Popup
(The ToggleButton, which opens/closes the Popup, is defined in a separate control ControlWithPopupButton, see below)

<Window>
  <Window.Triggers>

    <!-- 
      EventTriggers must be defined in the scope of the Popup "RtWindowBoundsPopup" 
      and in the routing path of the raised event.
    -->
    <EventTrigger RoutedEvent="ToggleButton.Unchecked" 
                  Sourcename="PopupButton">      
      <BeginStoryboard>
        <Storyboard>
          <BooleanAnimationUsingKeyFrames Storyboard.TargetName="RtWindowBoundsPopup"
                                          Storyboard.TargetProperty="IsOpen"
                                          Duration="0">
            <DiscreteBooleanKeyFrame Value="False" />
          </BooleanAnimationUsingKeyFrames>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>

    <EventTrigger RoutedEvent="ToggleButton.Checked" 
                  Sourcename="PopupButton">      
      <BeginStoryboard>
        <Storyboard>
          <BooleanAnimationUsingKeyFrames Storyboard.TargetName="RtWindowBoundsPopup"
                                          Storyboard.TargetProperty="IsOpen"
                                          Duration="0">
            <DiscreteBooleanKeyFrame Value="True" />
          </BooleanAnimationUsingKeyFrames>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Window.Triggers>

  <StackPanel>
    <ControlWithPopupButton />
    <Popup x:Name="RtWindowBoundsPopup">
      <ElementTreeControl Tag="pmButton" />
    </Popup>
  </StackPanel>
</Window>

The UserControl, which contains the PopupButton

<ControlWithPopupButton>

  <!-- 
    PopupButton must derive from ToggleButton
    or raise both Routed Events ToggleButon.Check and ToggleButton.Unchecked. 
  -->
  <PopupButton x:Name="PopupButton" />
</ControlWithPopupButton>

Remarks

Then methods like ElementTreeControl.Init, should be called from the ElementTreeControl.Loaded event handler inside the ElementTreeControl class. I don't now what StarElement is, but it should bind to a DependencyProperty of ElementTreeControl. I don't have enough context, but I guess you should add an override of ElementTreeControl.OnSelectionChanged, so that you can move the code of PopupListBoxSelectionChangedAsync to the ElementTreeControl.


Upvotes: 1

Related Questions