Keep submenu open when a new image is selected for display - wpf

Using WPF: A Simple Color Picker With Preview, Sacha Barber, 18 Apr 2012 ,
I created a custom control from it:
public class ColorCustomControl : Control
{....}
It is then used as:
<Menu....>
<MenuItem.....>
<pn:ColorCustomControl/>
</MenuItem>
</Menu>
This yields the following picture when the Brushes MenuItem is selected:
Selection of any item in the opened Brushes submenu results in the appropriate action being taken with the Brushes submenu REMAINING OPEN. This is the effect I want.
However, as shown below, selection of any of the three swatches results in a quick flicker of the new swatch -- it replaces the color pattern to the left of "Preview"--followed immediately by closure of the Brushes submenu.
If the Brushes menuitem is again chosen, the most recently selected swatch correctly appears.
I have tried all preview events (i.e., keyboard lost focus, left mouse down, etc.), to try stopping closure of the submenu when a swatch is chosen. Nothing I have found will stop the popup from closing.
How can closure of the Brushes submenu be prevented when selecting a swatch from the visual?
(I strongly suspect that redrawing of the visual, as in InvalidateVisual() when a new swatch image is selected, is forcing closure of the submenu).
Any ideas anybody?
TIA

My suggestion is to stop events propagation from your user control. So in your ColorCustomControl class first of all add a property (it can be a dependency one too if you need):
private bool propagateEvents = true;
public bool PropagateEvents
{
get
{
return propagateEvents;
}
set
{
propagateEvents = value;
}
}
Then add e.Handled = !PropagateEvents; at the end of every mouse event handler; in the end add a Swatch_MouseLeftButtonUp method (it has to handle the event raised by ImgSqaure1, ImgSqaure2 and ImgCircle1).
The result will be:
private void Swatch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Image img = (sender as Image);
ColorImage.Source = img.Source;
e.Handled = !PropagateEvents;
}
private void Swatch_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
e.Handled = !PropagateEvents;
}
private void CanvImage_MouseDown(object sender, MouseButtonEventArgs e)
{
IsMouseDown = true;
e.Handled = !PropagateEvents;
}
private void CanvImage_MouseUp(object sender, MouseButtonEventArgs e)
{
IsMouseDown = false;
e.Handled = !PropagateEvents;
}
and in the user control XAML:
<Image x:Name="ImgSqaure1"
Height="20" Width="20"
Source="Images/ColorSwatchSquare1.png"
Margin="45,0,0,0"
ToolTip="Square swatch1"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"
MouseLeftButtonUp="Swatch_MouseLeftButtonUp"/>
<Image x:Name="ImgSqaure2"
Height="20" Width="20"
Source="Images/ColorSwatchSquare2.png" Margin="5,0,0,0"
ToolTip="Square swatch2"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"
MouseLeftButtonUp="Swatch_MouseLeftButtonUp"/>
<Image x:Name="ImgCircle1" Height="20" Width="20"
Source="Images/ColorSwatchCircle.png" Margin="5,0,0,0"
ToolTip="Circle swatch1"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"
MouseLeftButtonUp="Swatch_MouseLeftButtonUp" />
Now all you have to do is set the PropagateEvents property in your menu:
<Menu....>
<MenuItem.....>
<pn:ColorCustomControl PropagateEvents="False" />
</MenuItem>
</Menu>
I hope it can help you.

Related

mahapps SplitButton expand on click

I defined a SplitButton in WPF(C#) with a binded item source.
The item list will be expanded only when the user clicks on the arrow at the right of the SplitButton.
How to expand the list when the user clicks on the SplitButton area?
I tried to handle the click event and set the property IsExpanded=true, but it automatically disappears after one second.
<Controls:SplitButton Name="SplitButton_Test"
Width="100"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left"
ItemsSource="{Binding Dictionary_Test}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
Click="Test_Click">
<Controls:SplitButton.Icon>
<iconPacks:PackIconMaterial Margin="6" Kind="Alert" />
</Controls:SplitButton.Icon>
</Controls:SplitButton>
private void Test_Click(object sender, RoutedEventArgs e)
{
if (SplitButton_Test.IsExpanded == false)
{
e.Handled = true;
SplitButton_Test.IsExpanded = true; //Doesn't work, closes automatically after 1 second
}
}
This code in MahApps is closing it:
//Make popup close even if no selectionchanged event fired (case when user select the save item as before)
void ListBoxPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var item = ContainerFromElement(_listBox, e.OriginalSource as DependencyObject) as ListBoxItem;
if (item != null)
{
IsExpanded = false;
}
}
currently at https://github.com/MahApps/MahApps.Metro/blob/develop/src/MahApps.Metro/MahApps.Metro.Shared/Controls/SplitButton.cs#L339
You need to remove that event handler. How? That's whole other question.

Button on ListBox Item

I have a ListBox filled with items. Some of the items may have buttons or links within RichTextBlock inside. I want to fire different action either when the item is pressed or when the button is pressed. The problem is when I hit the button, also the action connected with the item itself is fired. How can I prevent fireing the event of list item when the button inside is pressed?
ListBox:
<ListBox x:Name="StripesList" SelectionChanged="StripesList_SelectionChanged">
</ListBox>
ListBox Items:
<Border x:Name="OuterBorder" Width="400">
<Image x:Name="ThumbnailBox" Width="100" Source="{Binding Thumbnail}" Tap="ThumbnailClick" />
</Border>
Code:
private void StripesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (StripesList != null)
{
StripesList.SelectedIndex = -1;
}
}
private void ThumbnailClick(object sender, System.Windows.Input.GestureEventArgs e)
{
Image image = sender as Image;
// handle image click
}
private void ItemClick(object sender, EventArgs e)
{
// handle item click
}
I register item click event observers from code behind:
item.Tap += new EventHandler(ItemClick);
set e.Handled=true in the handler of all events . It will stop the Bubbleing of the event.

WPF: MouseEnter doesn't work on several buttons when mouse is pressed

I have a list of toggle-buttons in wpf and I want the user to be able to toggle several buttons by dragging over them. To do this, I used the MouseEnter-Event for each button. This does work, when I press the mousebutton outside the buttons and start dragging. But when I press the mousebutton on a button and start dragging, the MouseEnter-Event is only fired for the first button, where I pressed the mousebutton (also none of the other events like mouseover or mousemove are fired).
Here's the code:
public void AddButton()
{
ToggleButton btn = new ToggleButton();
btn.MouseEnter += VisibilityButton_Enter;
this.gridButtons.Children.Add(btn);
}
private void VisibilityButton_Enter(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed || e.RightButton == MouseButtonState.Pressed)
{
ToggleButton btn = sender as ToggleButton;
btn.IsChecked = !btn.IsChecked;
}
}
I found a solution to use "drag and drop" and the dragover-event, but I think there must be an easier solution?
As Kent mentioned, the ToggleButton captures the mouse. If we handle the PreviewMouseDown event ourselves we can prevent that. The rest is just keeping track of the mouse state so the we don't click twice during a single roll-over. Here is a behavior you can add to your button to enable roll-over clicking.
First add this namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and the corresponding reference to your project.
Then the XAML looks like this (notice the RollOverBehavior):
<Grid>
<ItemsControl>
<ItemsControl.ItemsSource>
<PointCollection>
<Point/>
<Point/>
<Point/>
<Point/>
<Point/>
</PointCollection>
</ItemsControl.ItemsSource>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Width="25" Height="25">
<i:Interaction.Behaviors>
<local:RollOverBehavior/>
</i:Interaction.Behaviors>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and here is the behavior itself:
public class RollOverBehavior : Behavior<ToggleButton>
{
bool mouseOver;
bool clicked;
protected override void OnAttached()
{
AssociatedObject.PreviewMouseLeftButtonDown += (s, e) =>
{
AssociatedObject.IsChecked = !AssociatedObject.IsChecked;
e.Handled = true;
};
AssociatedObject.MouseEnter += (s, e) =>
{
mouseOver = true;
clicked = false;
};
AssociatedObject.MouseLeave += (s, e) =>
{
mouseOver = false;
};
AssociatedObject.MouseMove += (s, e) =>
{
if (mouseOver && !clicked && e.LeftButton == MouseButtonState.Pressed)
{
AssociatedObject.IsChecked = !AssociatedObject.IsChecked;
clicked = true;
}
};
}
}
The problem is that the default behavior of the ToggleButton is to capture the mouse when the left mouse button is clicked. Because the mouse is captured, all mouse events are sent to the first ToggleButton.
Sounds like what you want to do is override this default behavior such that the mouse isn't captured, but to be honest I couldn't really follow exactly what it is you're trying to achieve.
I had the same problem with normal Buttons. The solution, that worked for me, is to set e.Handled = true in the PreviewMouseButtonDown event (I implemented this too). It seems, that just by down-click with the mouse, the previous action is not fully handled until the mouse button is released, so the MouseEnter event is not able to raise.

WPF ListBox drag & drop interferes with ContextMenu?

I'm implementing drag & drop from a ListBox, but I'm seeing some strange behaviour with a ContextMenu elsewhere in the window. If you open the context menu and then start a drag from the ListBox, the context menu closes but won't open again until after you perform another drag.
Does this make sense? Anybody got any ideas what might be going on?
<ListBox Grid.Row="0" ItemsSource="{Binding SourceItems}" MultiSelectListboxDragDrop:ListBoxExtension.SelectedItemsSource="{Binding SelectedItems}" SelectionMode="Multiple" PreviewMouseLeftButtonDown="HandleLeftButtonDown" PreviewMouseLeftButtonUp="HandleLeftButtonUp" PreviewMouseMove="HandleMouseMove"/>
<ListBox Grid.Row="1" ItemsSource="{Binding DestinationItems}" AllowDrop="True" Drop="DropOnToDestination" />
<Button Grid.Row="2">
<Button.ContextMenu>
<ContextMenu x:Name="theContextMenu">
<MenuItem Header="context 1"/>
<MenuItem Header="context 2"/>
<MenuItem Header="context 3"/>
</ContextMenu>
</Button.ContextMenu>
Button with context menu
</Button>
...
public partial class Window1
{
private bool clickedOnSourceItem;
public Window1()
{
InitializeComponent();
DataContext = new WindowViewModel();
}
private void DropOnToDestination(object sender, DragEventArgs e)
{
var viewModel = (WindowViewModel)e.Data.GetData(typeof(WindowViewModel));
viewModel.CopySelectedItems();
}
private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sourceElement = (FrameworkElement)sender;
var hitItem = sourceElement.InputHitTest(e.GetPosition(sourceElement)) as FrameworkElement;
if(hitItem != null)
{
clickedOnSourceItem = true;
}
}
private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
{
clickedOnSourceItem = false;
}
private void HandleMouseMove(object sender, MouseEventArgs e)
{
if(clickedOnSourceItem)
{
var sourceItems = (FrameworkElement)sender;
var viewModel = (WindowViewModel)DataContext;
DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
clickedOnSourceItem = false;
}
}
}
It seemed to be something to do with the mouse capture!?
The normal sequence of events during a drag goes something like this...
The PreviewMouseLeftButtonDown
handler gets called and
ListBox.IsMouseCaptureWithin is
false.
The PreviewMouseMove handler
gets called. By this time
ListBox.IsMouseCaptureWithin is true.
During the PreviewMouseMove handler
DragDrop.DoDragDrop gets called and
sometime during this the mouse
capture is released from the ListBox.
But, what seems to happening for a drag started when the context menu is open is...
The PreviewMouseLeftButtonDown
handler gets called and
ListBox.IsMouseCaptureWithin is
false.
The PreviewMouseMove handler gets
called. But this time
ListBox.IsMouseCaptureWithin is
still false.
Sometime after the end of the
PreviewMouseMove handler the
ListBox then gets the mouse capture
(ListBox.IsMouseCaptureWithin
becomes true)
The result of this is that after the drag, the ListBox still has the mouse capture so any clicks on the button to open the context menu are actually going to the listbox not the button.
Adding the following code to the start of the PreviewMouseLeftButtonDown handler seems to help by swallowing up the click that closes that context menu rather than trying to start a drag from it...
if (!contextMenuCloseComplete)
{
sourceElement.CaptureMouse();
return;
}
...with the contextMenuCloseComplete bool getting set in handlers for the context menu's Closed and Opened events.
Does that make sense? Does anyone understand where this mouse capture behaviour is coming from?

How to add a focus style to an editable ComboBox in WPF

I've been looking at the following example on how to style the ComboBox, but I haven't been able to create a focus effect when going into an editable combo box. Whenever the ComboBox receives focus, it should go into edit mode and the component should have a focus style.
The basic problem is that whenever I go into the edit mode, it's not the surrounding ComboBox which actually has the focus, but the text subcomponent and I haven't been able to create a Trigger on the text component which modifies the ComboBox's border style since I don't know how to refer to the parent component from the trigger.
I've tried adding ControlTemplate Trigger on the TextBox, or style trigger. I've tried to refer to the ComboBox by name or by using the TemplateBinding option, but without any luck. A simple example would be very appreciated.
Bind IsKeyboardFocusWithin to IsDropDownOpen
<ComboBox ItemsSource="{Binding SortedItems}"
StaysOpenOnEdit="True"
IsDropDownOpen="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Self}, Mode=OneWay}" />
private void cmbSpecialHandling_GotFocus(object sender, RoutedEventArgs e)
{
Thickness th = new Thickness(2);
cmbSpecialHandling.BorderThickness = th;
cmbSpecialHandling.BorderBrush = this.FindResource("TabFocusColor") as SolidColorBrush;
}
private void cmbSpecialHandling_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
Thickness th = new Thickness(2);
cmbSpecialHandling.BorderThickness = th;
cmbSpecialHandling.BorderBrush = this.FindResource("TabFocusColor") as SolidColorBrush;
}
private void cmbSpecialHandling_LostFocus(object sender, RoutedEventArgs e)
{
cmbSpecialHandling.BorderBrush = Brushes.Transparent;
}
Set the border brush of combobox in its Gotfocus and make it transparent in lost focus:
private void comboBox_GotFocus(object sender, RoutedEventArgs e)
{
Thickness th = new Thickness(2);
comboBox.BorderThickness = th;
comboBox.BorderBrush = this.FindResource("TabFocusColor") as SolidColorBrush;
or
comboBox.BorderBrush = Brushes.Green;
}
private void comboBox_LostFocus(object sender, RoutedEventArgs e)
{
comboBox.BorderBrush = Brushes.Transparent;
}

Resources