Simple question though. I have a WPF application (.NET 4.0). There is a listbox which contains a number of userpanels. Each of these userpanels contains a checkbox.
When running you can click any portion of the userpanel except the checkbox itself and the listbox will select that row (which is indicated visually by the background changing in this simple case). If you check the box the row is not selected.
Requirement:
If you check the checkbox, this should count as selecting the row.
Checkbox Control:
<UserControl x:Class="CheckboxClickExample.CheckboxControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="189" d:DesignWidth="221">
<Grid>
<CheckBox Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="10,10,0,0" Name="checkBox1" VerticalAlignment="Top" />
</Grid>
</UserControl>
Main Window:
<Window x:Class="CheckboxClickExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:checkboxsample="clr-namespace:CheckboxClickExample"
Title="MainWindow" Height="350" Width="525">
<ListBox>
<checkboxsample:CheckboxControl/>
<checkboxsample:CheckboxControl/>
<checkboxsample:CheckboxControl/>
<checkboxsample:CheckboxControl/>
</ListBox>
</Window>
You could handle this in your UserControl code behind:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var parent = sender as DependencyObject;
while (parent != null)
{
if (parent is Selector)
break;
parent = VisualTreeHelper.GetParent(parent);
}
if (parent != null)
((Selector) parent).SelectedItem = this;
}
And then use the handler in your CheckBox:
<CheckBox Content="CheckBox"
Height="16"
Click="ButtonBase_OnClick"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top" />
Edit
If you don't want to use code behind, the best I think you can do is to package the existing solution as an attached behaviour. This has the benefit that you only have to write the code once, and that the property can be set on any button even if it is not part of a UserControl.
For example:
public static class ButtonClickHelper
{
public static void SetEnableSelectionOnClick(ButtonBase button, bool value)
{
button.SetValue(EnableSelectionOnClickProperty, value);
}
public static bool GetEnableSelectionOnClick(ButtonBase button)
{
return (bool) button.GetValue(EnableSelectionOnClickProperty);
}
public static readonly DependencyProperty EnableSelectionOnClickProperty =
DependencyProperty.RegisterAttached("EnableSelectionOnClick", typeof (bool), typeof (ButtonClickHelper),
new FrameworkPropertyMetadata(OnEnableSelectionOnClickPropertyChanged));
private static void OnEnableSelectionOnClickPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (!(d is ButtonBase))
return;
var button = (ButtonBase) d;
if ((bool) e.NewValue)
{
button.Click += OnButtonClick;
}
else
{
button.Click -= OnButtonClick;
}
}
private static void OnButtonClick(object sender, RoutedEventArgs e)
{
var parent = sender as DependencyObject;
var ancestors = new List<DependencyObject>();
while (parent != null)
{
if (parent is Selector)
break;
parent = VisualTreeHelper.GetParent(parent);
ancestors.Add(parent);
}
if (parent != null)
{
var selector = (Selector) parent;
var itemToSelect = ancestors.Where(i => selector.Items.Contains(i)).FirstOrDefault();
if (itemToSelect != null)
((Selector) parent).SelectedItem = itemToSelect;
}
}
}
Then you can use this in your XAML by just setting the EnableSelectionOnClick dependency property:
<CheckBox Content="CheckBox"
Height="16"
l:ButtonClickHelper.EnableSelectionOnClick="True"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top" />
Hope this helps!
Related
Got a WPF application which has an on hover popup. The popup contains a list of different files which can be opened (e.g. pdf, excel etc)
You can navigate and select a file by double clicking and it opens as you would expect.
But if I now navigate to a different file I can see that the on hover selection isn't now working,
If you now select a different file, the original file is opened again.
I am using a Process.Start and passing the full path to the file to the method.
The application is a fair size so here are some excerpts for a Test application I have written to look into this further
The XAML for the main window
<Window x:Class="TestPopupIssue.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas Margin="5" Background="Red" Width="200" Height="150" >
<Rectangle Name="rectangle1"
Canvas.Top="60" Canvas.Left="50"
Height="85" Width="60"
Fill="Black" MouseEnter="rectangle1_MouseEnter" MouseLeave="rectangle1_MouseLeave" />
<Popup x:Name="PopupWindow" PlacementTarget="{Binding ElementName=rectangle1}" Placement="Top" MouseEnter="rectangle1_MouseEnter" MouseLeave="rectangle1_MouseLeave">
<ListBox MinHeight="50" ItemsSource="{Binding Files}" MouseDoubleClick="FileList_MouseDoubleClick"`enter code here` x:Name="FileList" />
</Popup>
</Canvas>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
FileList f;
public MainWindow()
{
InitializeComponent();
f = new FileList();
f.PopulateFiles();
this.DataContext = f;
}
private void FileList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (FileList.SelectedItem != null)
{
string item = FileList.SelectedItem as string;
if (item != null)
{
System.Diagnostics.Process.Start(item);
}
}
}
private void rectangle1_MouseEnter(object sender, MouseEventArgs e)
{
PopupWindow.IsOpen = true;
}
private void rectangle1_MouseLeave(object sender, MouseEventArgs e)
{
PopupWindow.IsOpen = false;
}
}
And there is a FileList class which just has a generic string list of file paths called
Files
Thanks
I have tested your Sample-Application, when your opening the File with Process.Start your Focus gets stolen by the Application that opens your File.
Somehow the ListBox in the Popup canĀ“t change their SelectedItem when the Window has lost his Focus.
Unfortunately I have not managed to get the focus back on the Window, this.SetFocus() has not worked for me.
Anyway another possible Solution would be to close the Popup when your opening the File.
private void FileList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (FileList.SelectedItem != null)
{
string item = FileList.SelectedItem as string;
if (item != null)
{
System.Diagnostics.Process.Start(item);
PopupWindow.IsOpen = false;
}
}
}
this way the ListBox can update the selectedItem again.
hope this helps!
I have a dialog with few controls. There is a TextBox named txtControl and two Buttons Accept and Cancel. I want that once the focus is in txtControl, the focus should not go away, until I click on Accept or Cancel button.
If I try to click on any other control without clicking on Accept or Cancel button, then focus should remains in txtControl. Also I don't want to disable or gray out other controls.
You might handle OnPreviewMouseDown in the root, whenever focus is on txtControl, and the mouse is not over txtControl, Accept or Cancel;
void mainWindow_previewMouseDown(object sender, MouseEventArg e)
{
if (txtControl.IsFocusWithin)
{
if (txtControl.IsMouseOver == false ||
accept.IsMouseOver ==false ||
cancel.IsMouseOver ==false)
{
e.Handle = true;
}
}
}
and you might also hadle PreviewKeyDown to see if Tab is Pressed or not.
I would create an attached property that looked for the textbox losing Keyboad focus and just force focus back in to the textbox again.
The attached property would be something like this.
public class TextBoxExtras : DependencyObject
{
public static bool GetRetainsFocus(DependencyObject obj)
{
return (bool)obj.GetValue(RetainsFocusProperty);
}
public static void SetRetainsFocus(DependencyObject obj, bool value)
{
obj.SetValue(RetainsFocusProperty, value);
}
public static readonly DependencyProperty RetainsFocusProperty =
DependencyProperty.RegisterAttached("RetainsFocus", typeof(bool), typeof(TextBoxExtras), new PropertyMetadata(false, new PropertyChangedCallback((s, e) =>
{
TextBox textBox = s as TextBox;
if (textBox != null)
{
if (!(bool)e.NewValue && (bool)e.OldValue)
textBox.LostKeyboardFocus -= textBox_LostKeyboardFocus;
if ((bool)e.NewValue)
{
textBox.LostKeyboardFocus += textBox_LostKeyboardFocus;
textBox.Unloaded += textBox_Unloaded;
}
}
})));
static void textBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox != null )
if (textBox.Focusable)
textBox.Focus();
}
static void textBox_Unloaded(object sender, RoutedEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox != null)
{
textBox.LostKeyboardFocus -= textBox_LostKeyboardFocus;
textBox.Unloaded -= textBox_Unloaded;
}
}
}
And use it in XAML like this, the first textbox is the one that will "retain focus"
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Background="Black"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox local:TextBoxExtras.RetainsFocus="True" Margin="10,10,387,283"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="10,37,0,260" Width="120" />
<Button Content="Accept" HorizontalAlignment="Left" Margin="10,81,0,0" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>
This kind of restriction is not a good idea.
How will your app be used by someone who can't use a mouse and uses the tab key to move between controls?
You could handle the PreviewLostKeyboardFocus at root level.
In xaml
<Window ... PreviewLostKeyboardFocus="Win_PreviewLostKeyboardFocus">
In C#
private void Win_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// change focus behavior only when txtControl
// is the element losing focus
if (e.OldFocus != txtControl)
return;
// if new element with focus is not Accept and is not Cancel, then disable the focus change
if (e.NewFocus != Accept && e.NewFocus != Cancel)
e.Handled = true;
}
I can bind a combobox in the codebehind like this:
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
var combo = sender as ComboBox;
App.SchedulerVM = new ScheduleViewModel();
combo.DataContext = App.SchedulerVM;
combo.ItemsSource = App.SchedulerVM.Frequency;
}
This works - my combobox has the items from the Frequency List in the SchedulerVM object.
However, I don't want to do any of this in the codebehind. But the ways I've done this in WP7 before aren't working here. If I comment out the last line in the Loaded method above and try to set the ItemsSource in XAML, it doesn't work - nothing shows up:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
Frequency}" />
This doesn't work either:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
App.SchedulerVM.Frequency}" />
Nor this:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
SchedulerVM.Frequency}" />
Ideally, the DataContext wouldn't have to be explicitly set in the codebehind for this control either, it would be inherited from the LayoutRoot, where I've set it in the codebehind. But that's step 2 of my troubleshooting here.
What am I doing wrong? '
Thanks!
Edit
The ScheduleViewModel looks like this:
namespace SchedulerUI.ViewModels
{
public class ScheduleViewModel : INotifyPropertyChanged
{
//private properties
private Schedule _thisSchedule;
//public properties
public Schedule ThisSchedule
{
get { return _thisSchedule; }
set
{
if (value != _thisSchedule)
{
NotifyPropertyChanged("ThisSchedule");
}
_thisSchedule = value;
}
}
public List<string> Frequency = new List<string>();
public string Test;
//constructors
public ScheduleViewModel()
{
Frequency.AddRange(new string[] { "Daily", "Weekly", "Monthly" });
Test = "This is only a test.";
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here's the entire XAML:
<UserControl x:Class="SchedulerUI.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<ComboBox Height="23" HorizontalAlignment="Left" Margin="34,41,0,0" Name="comboBox1" Loaded ="comboBox1_Loaded" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Frequency}" />
<TextBox BorderBrush="Black" HorizontalAlignment="Left" Margin="34,41,0,0" Width="100" Height="100" DataContext="LayoutRoot.DataContext" Text="{Binding Test}" />
</Grid>
</UserControl>
Here's the entire codebehind:
namespace SchedulerUI
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
App.SchedulerVM = new ScheduleViewModel();
comboBox1.DataContext = App.SchedulerVM;
List<string> testlist = App.SchedulerVM.Frequency;
string teststring = App.SchedulerVM.Test;
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
//App.SchedulerVM = new ScheduleViewModel();
//var root = sender as Grid;
//if (root != null)
//{
// root.DataContext = App.SchedulerVM;
//}
}
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
//var combo = sender as ComboBox;
//App.SchedulerVM = new ScheduleViewModel();
//combo.DataContext = App.SchedulerVM;
//combo.ItemsSource = App.SchedulerVM.Frequency;
}
}
}
You binding is not working, because:
when you set ItemsSource in XAML its get executed first and it tries to bind the wrong/empty DataContext
then the Loaded event is raised which will set the correct DataContext but your already existing binding won't be refreshed automatically.
If you have to set the DataContext in the codebehind do it in your views constructor:
public YourView()
{
InitializeComponent();
combo.DataContext = App.SchedulerVM;
}
Then the following binding should work:
<ComboBox Name="comboBox1" ItemsSource="{Binding Frequency}" />
The databinding in WPF/Silverlight needs public properties. Currently Frequency is a public field on your viewmodel change it to a property and everthing should work:
private List<string> frequency = new List<string>();
public List<string> Frequency { get { return frequency; } set { frequency = value; }
And that is why it worked your initial loaded event because you didn't used databind there but you just set the combo.ItemsSource.
I am writing a Silverlight for Windows Phone 7.5 app.
I want to reference the ContextMenu inside my LongListSelector because I want to set .IsOpen to false as soon as the ContextMenu Click event is called. My thought was that this should happen automatically but it does not.
One of my MenuItem's sets the visibility of a <Grid> from collapsed to visible which mimics a PopUp. Whilst the code executes fine and the Visibility does indeed change. The UI of the app does not show the Grid unless the ContextMenu closes.
My XAML of the LongListSelector which contains a ContextMenu called Menu that I wish to reference in the ContextMenuItem Click event.
<toolkit:LongListSelector x:Name="moviesLongList" Background="Transparent" IsFlatList="False" GroupHeaderTemplate="{StaticResource GroupHeaderTemplate}" GroupItemTemplate="{StaticResource GroupItemTemplate}" SelectionChanged="moviesLongList_SelectionChanged" GroupViewClosing="moviesLongList_GroupViewClosing" GroupViewOpened="moviesLongList_GroupViewOpened">
<toolkit:LongListSelector.GroupItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</toolkit:LongListSelector.GroupItemsPanel>
<toolkit:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Height="91" Margin="20,0,0,20" Orientation="Horizontal">
<toolkit:ContextMenuService.ContextMenu >
<toolkit:ContextMenu x:Name="Menu" Opened="ContextMenu_Opened" Loaded="Menu_Loaded" Unloaded="Menu_Unloaded">
<toolkit:ContextMenu.ItemTemplate>
<DataTemplate>
<toolkit:MenuItem Header="{Binding}" Click="ContextMenuButton_Click" LostFocus="MenuItem_LostFocus" />
</DataTemplate>
</toolkit:ContextMenu.ItemTemplate>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<Border HorizontalAlignment="Left" Width="61" Height="91" Background="{Binding ID, Converter={StaticResource ThumbImageConvert}}" />
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" Width="395">
<TextBlock x:Name="titleTextBox" Text="{Binding Title, Converter={StaticResource TitleConvert}}" Margin="6,0,6,0" d:LayoutOverrides="Width" FontSize="{StaticResource PhoneFontSizeLarge}" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<TextBlock x:Name="yearTextBox" Text="{Binding Year}" Margin="12,0,0,0" HorizontalAlignment="Left" FontSize="{StaticResource PhoneFontSizeMedium}" Foreground="{StaticResource PhoneSubtleBrush}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</toolkit:LongListSelector.ItemTemplate>
</toolkit:LongListSelector>
My code behind ContextMenuItem Click event
private void ContextMenuButton_Click(object sender, RoutedEventArgs e)
{
//
// This is where I want to set Menu.IsOpen = false to close the ContextMenu.
//
if ((sender as MenuItem).Header.ToString() == "lend movie")
{
DisableAppBarIcons();
LendPopUpOverlay.Visibility = System.Windows.Visibility.Visible;
}
if ((sender as MenuItem).Header.ToString() == "return to collection")
{
... Do stuff
}
if ((sender as MenuItem).Header.ToString() == "add to boxset")
{
... Do stuff
}
if ((sender as MenuItem).Header.ToString() == "delete")
{
... Do stuff
}
}
I set the ItemSource of the ContextMenu in the ContextMenu_Opened event. The fields are both of type List<String>.
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
LentMovieObj = (sender as ContextMenu).DataContext as Movies;
if (LentMovieObj.IsLent)
{
(sender as ContextMenu).ItemsSource = menuItemsReturn;
}
else
{
(sender as ContextMenu).ItemsSource = menuItemsLendOut;
}
}
Not sure why the ContextMenu is not closing, but here are two solutions. The first is to get the parent of the MenuItem.
private T GetParentOfType<T>(DependencyObject obj) where T : class
{
if (obj == null) return null;
var parent = VisualTreeHelper.GetParent(obj);
while (parent != null)
{
if (parent is T) return parent as T;
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
Then get the Menu from your click handler
var menu = GetParentOfType<ContextMenu>(sender as MenuItem);
menu.IsOpen = false;
The second solution is to bind IsOpen to a backing viewmodel
<toolkit:ContextMenuService.ContextMenu >
<toolkit:ContextMenu x:Name="Menu" Opened="ContextMenu_Opened" Loaded="Menu_Loaded" Unloaded="Menu_Unloaded" IsOpen="{Binding IsOpen}" ItemsSource="{Binding Items}">
<toolkit:ContextMenu.ItemTemplate>
<DataTemplate>
<toolkit:MenuItem Header="{Binding}" Click="ContextMenuButton_Click" LostFocus="MenuItem_LostFocus" />
</DataTemplate>
</toolkit:ContextMenu.ItemTemplate>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
Change your open event:
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
LentMovieObj = (sender as ContextMenu).DataContext as Movies;
if (LentMovieObj.IsLent)
{
(sender as ContextMenu).DataContext = new ContextMenuViewModel(menuItemsReturn);
}
else
{
(sender as ContextMenu).DataContext = ContextMenuViewModel(menuItemsLendOut);
}
}
Then a viewmodel
public class ContextMenuViewModel : INotifyPropertyChanged
{
private bool _isOpen = true;
public ContextMenuViewModel(IEnumerable<string> items)
{
Items = items;
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsOpen
{
get { return _isOpen; }
set { _isOpen = value; OnPropertyChanged("IsOpen"); }
}
public IEnumerable<String> Items { get; set; }
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then set the IsOpen property of your ViewModel to false;
Is there any way to change the Content of a RibbonButton?
I know that a RibbonButton has an Image and a Label but I want a button with a shape (Rectangle class) in it's Content and this button should have the RibbonButton style applied. Is there any way to do that?
Why don't you create your own button control?
XAML:
<Button x:Class="MyApp.myButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="71" d:DesignWidth="87">
<Rectangle Height="40" Width="40" Fill="#FFC11414" />
Code behind:
public partial class myButton : Button, IRibbonControl
{
public myButton()
{
InitializeComponent();
Type forType = typeof(myButton);
FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(forType, new FrameworkPropertyMetadata(forType));
ButtonBase.CommandProperty.OverrideMetadata(forType, new FrameworkPropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));
FrameworkElement.ToolTipProperty.OverrideMetadata(forType, new FrameworkPropertyMetadata(null, new CoerceValueCallback(RibbonButton.CoerceToolTip)));
ToolTipService.ShowOnDisabledProperty.OverrideMetadata(forType, new FrameworkPropertyMetadata(true));
}
public static object CoerceToolTip(DependencyObject d, object value)
{
if (value == null)
{
RibbonButton button = (RibbonButton)d;
RibbonCommand command = button.Command as RibbonCommand;
if ((command == null) || ((string.IsNullOrEmpty(command.ToolTipTitle) && string.IsNullOrEmpty(command.ToolTipDescription)) && (command.ToolTipImageSource == null)))
{
return value;
}
value = new RibbonToolTip(command);
}
return value;
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((myButton)d).CoerceValue(FrameworkElement.ToolTipProperty);
}
}