Code behind:
public class LoginButton : Button
{
public static DependencyProperty LoginedProperty;
public static DependencyProperty LoginEventProperty;
public delegate void LoginEventDelegate(object sender, RoutedEventArgs e);
static LoginButton()
{
LoginedProperty = DependencyProperty.Register("Logined", typeof(Boolean), typeof(LoginButton));
LoginEventProperty = DependencyProperty.Register("LoginEvent", typeof(LoginEventDelegate), typeof(LoginButton));
}
public Boolean Logined
{
get { return (Boolean)base.GetValue(LoginedProperty); }
set { base.SetValue(LoginedProperty, value); }
}
public event LoginEventDelegate LoginEvent;
protected virtual void OnLoginEvent(object sender, RoutedEventArgs e)
{
if (LoginEvent != null)
LoginEvent(sender,e);
}
}
XAML:
<ControlTemplate x:Key="LoginButtonTemplate" TargetType="local:LoginButton">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ContentPresenter VerticalAlignment="Center" Margin="10,0,0,0"></ContentPresenter>
<Grid Grid.Column="1" Margin="5,0,0,0">
<Label Name="L" VerticalAlignment="Center" Padding="0" Foreground="#919191" Visibility="Collapsed">logined</Label>
<Button Name="B" Template="{StaticResource CustomButton}" Background="Transparent" BorderThickness="0" Padding="0" Foreground="#3598db" Content="click login"></Button>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Logined" Value="true">
<Setter TargetName="L" Property="Visibility" Value="Visible"></Setter>
<Setter TargetName="B" Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
--------------------------------
I want to achieve this function:
The button which Name="B" in ControlTemplate,when I click it,it work the custom event OnLoginEvent?
How can i do it?
--------------------------------
Oh,it seems no one knows my meaning for my poor English.
Now I explain the function for more detailed.
The software needs to manage about 100 or more account,if the account is not logined,it will show the button let user to login.And if is logined it will show the label that is logined
So I make a new Custom usercontrol named LoginButton,and create a boolean 'Logined' to control button if is logined.
Beaucase of different account has different login function.So I create a new event 'LoginEvent' to apply different login function.Now the question is the button Name="B" which to login.I need binding the button Name="B" click event or previewmousedown event to the event 'LoginEvent'.But I can't find the way to binding it.
Please help me,thanks a lot.
You could override the OnApplyTemplate method of the LoginButton class and hook up an event handler to the click event for the "B" button in the template that raises the event.
LoginEvent should be an event and not a dependency property though.
Try this:
public class LoginButton : Button
{
public static DependencyProperty LoginedProperty;
public delegate void LoginEventDelegate(object sender, RoutedEventArgs e);
static LoginButton()
{
LoginedProperty = DependencyProperty.Register("Logined", typeof(Boolean), typeof(LoginButton));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button b = this.Template.FindName("B", this) as Button;
if(b != null)
{
b.Click += (s, e) => OnLoginEvent(s, e);
}
}
public Boolean Logined
{
get { return (Boolean)base.GetValue(LoginedProperty); }
set { base.SetValue(LoginedProperty, value); }
}
public event LoginEventDelegate LoginEvent;
protected virtual void OnLoginEvent(object sender, RoutedEventArgs e)
{
if (LoginEvent != null)
LoginEvent(sender, e);
}
}
Usage:
<local:LoginButton Template="{StaticResource LoginButtonTemplate}" LoginEvent="LoginButton_LoginEvent"/>
private void LoginButton_LoginEvent(object sender, RoutedEventArgs e)
{
MessageBox.Show("Login!");
}
Related
`I am working on a WPF application (MVVM)
I have a user control(uc1) that has four buttons. cancel,accept,exit
I am going to use this control in multiple views.
I need to cancel button to revert the changes what user will make in propertygrig
user control:
<UserControl x:Class="WPF.CustomControl.RadPropertyWindowButtons"
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="45" d:DesignWidth="700">
<Grid>
<Grid Uid="radpropertybuttons" Height="39" VerticalAlignment="Bottom" Margin="74,0,-108,0">
<Button x:Name="Cancel"
Command="{Binding radpropertyCancel}" >
</Button>
<Button x:Name="Accept"
Command="{Binding radpropertyAccept}">
</Button>
<Button x:Name="Exit"
Command="{Binding radpropertyExit}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
</Button>
</Grid>
</Grid>
</UserControl>
view:
<Grid Height="564" VerticalAlignment="Top" >
<Grid HorizontalAlignment="Center">
<telerik:RadLayoutControl
Name="PropertyGridContainer"
Orientation="Vertical">
</telerik:RadLayoutControl>
</Grid>
<Grid VerticalAlignment="Bottom">
<customcontrol:RadPropertyWindowButtons x:Name="ucPropertyButtons" Height="44" VerticalAlignment="Top" HorizontalAlignment="Center" Loaded="RadPropertyWindowButtons_Loaded" />
</Grid>
</Grid>
in view model
public ICommand radpropertyCancel { get; set; }
radpropertyCancel = new ViewModelCommand(execradpropertyCancel);
private void execradpropertyCancel(object obj)
{
this.RevertToOriginalData();
}
how to clear the PropertyGridContainer and bind with the data that we get from RevertToOriginalData`
I do it like this if i do from code behind if i use click event but how to do it with command.
this._viewModel.RevertToOriginalData();
this.PropertyGridContainer.Items.Clear();
this.PropertyGridContainer.Items.Add(this._viewModel.myGrid);
this.ViewModel.IsDirty = false;
this._viewModel.myGrid is wrong design if myGrid is really a Grid ( a UI element). Your view model classes must never handle UI elements or participate in/implement UI logic.
Data changes are always handled outside the view (where the data lives). Layout on the other hand is always the domain of the view.
If you want to revert the layout changes made by the user, you must do this completely in the view (code-behind).
To accomplish this, a parent control (e.g., Window) that hosts both, the RadPropertyWindowButtons and the RadLayoutControl, should expose the related commands as routed commands.
Then in the command handlers you save (serialize) the layout before edit (or alternatively on accept/after edit) and restore (deserialize) it in case the edit procedure was cancelled. The RadLayoutControl exposes a related API to help with the serialization.
Now, that the implementation of the custom control no longer depends on the explicit view model class type, the RadPropertyWindowButtons has become fully reusable in any context.
In general, to enable reusability of controls they must express their (data) dependencies as dependency properties, that are later bound to the current DataContext. The internals of the reusable control simply bind to these dependency properties (instead of binding to an explicit DataContext type). Otherwise they are only "reusable" with a particular DataContext.
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedUICommand CancelEditLayoutCommand { get; } = new RoutedUICommand(
"Cancel layout edit and revert to previous state",
nameof(MainWindow.CancelEditLayoutCommand),
typeof(MainWindow));
public static RoutedUICommand AcceptLayoutCommand { get; } = new RoutedUICommand(
"Accept the current layout",
nameof(MainWindow.AcceptLayoutCommand),
typeof(MainWindow));
private Dictionary<RadLayoutControl, bool> IsInEditModeTable { get; }
private string SerializedLayout { get; set; }
public MainWindow()
{
InitializeComponent();
this.IsInEditModeTable = new Dictionary<RadLayoutControl, bool>();
var cancelEditLayoutCommandBinding = new CommandBinding(MainWindow.CancelEditLayoutCommand, ExecuteCancelEditLayoutCommand, CanExecuteCancelEditLayoutCommand);
_ = this.CommandBindings.Add(cancelEditLayoutCommandBinding);
var acceptLayoutCommandBinding = new CommandBinding(MainWindow.AcceptLayoutCommand, ExecuteAcceptLayoutCommand, CanExecuteAcceptLayoutCommand);
_ = this.CommandBindings.Add(acceptLayoutCommandBinding);
}
private void CanExecuteCancelEditLayoutCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = e.Parameter is RadLayoutControl targetControl
&& this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode;
private void ExecuteCancelEditLayoutCommand(object sender, ExecutedRoutedEventArgs e)
{
var targetControl = (RadLayoutControl)e.Parameter;
RestoreLayout(targetControl);
this.IsInEditModeTable[targetControl] = false;
}
private void CanExecuteAcceptLayoutCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = e.Parameter is RadLayoutControl targetControl
&& this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode;
private void ExecuteAcceptLayoutCommand(object sender, ExecutedRoutedEventArgs e)
{
var targetControl = (RadLayoutControl)e.Parameter;
SaveLayout(targetControl);
this.IsInEditModeTable[targetControl] = false;
}
// Instead of handling the SelectionChanged event I recommend
// to introduce another routed command that allows the user to put the RadLayoutControl into edit mode (by setting the RadLayoutControl.IsInEditMode accordingly).
// Aside from an improved UX this would provide a better flow or trigger to kickoff the serialization
private void OnLayoutControlSelectionChanged(object sender, LayoutControlSelectionChangedEventArgs e)
{
var targetControl = sender as RadLayoutControl;
if (this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode)
{
return;
}
isTargetControlInEditMode = e.NewItem is not null;
if (isTargetControlInEditMode)
{
SaveLayout(targetControl);
}
this.IsInEditModeTable[targetControl] = isTargetControlInEditMode;
}
private void SaveLayout(RadLayoutControl targetControl)
=> this.SerializedLayout = targetControl.SaveToXmlString();
private void RestoreLayout(RadLayoutControl targetControl)
=> targetControl.LoadFromXmlString(this.SerializedLayout);
}
MainWindow.xaml
<Window>
<StackPanel>
<telerik:RadLayoutControl Name="PropertyGridContainer"
IsInEditMode="True"
telerik:RadLayoutControl.SerializationId="PropertyGridContainerID"
SelectionChanged="OnLayoutControlSelectionChanged" />
<customcontrol:RadPropertyWindowButtons TargetControl="{Binding ElementName=PropertyGridContainer}" />
</StackPanel>
</Window>
RadPropertyWindowButtons.xaml.cs
class RadPropertyWindowButtons
{
public RadLayoutControl TargetControl
{
get => (RadLayoutControl)GetValue(TargetControlProperty);
set => SetValue(TargetControlProperty, value);
}
public static readonly DependencyProperty TargetControlProperty = DependencyProperty.Register(
"TargetControl",
typeof(RadLayoutControl),
typeof(RadPropertyWindowButtons),
new PropertyMetadata(default));
}
RadPropertyWindowButtons.xaml
<UserControl>
<StackPanel>
<Button x:Name="Cancel"
Command="{x:Static local:MainWindow.CancelEditLayoutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TargetControl}" />
<Button x:Name="Accept"
Command="{x:Static local:MainWindow.AcceptLayoutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TargetControl}" />
</StackPanel>
</UserControl>
See Save/Load Layout for more advanced scenarios.
I'm trying to create a custom dropdown control that acts like a ComboBox, such that the Popup opens when you click mouse down (not up), and closes when you click outside of the control.
The problem is that it only behaves if I set ClickMode to "Release". But what I really want is ClickMode="Press", such that the Popup opens on MouseDown instead of MouseUp.
But when I set it to ClickMode="Press", the popup won't close when you click outside the control.
Any ideas how I can achieve this?
Usage :
<StackPanel>
<local:CustomDropdown Width="200"
Height="50"
Content="Custom!" />
<ComboBox Width="200"
Margin="20">
<ComboBoxItem>A</ComboBoxItem>
<ComboBoxItem>B</ComboBoxItem>
<ComboBoxItem>C</ComboBoxItem>
</ComboBox>
</StackPanel>
Class :
internal class CustomDropdown : ContentControl
{
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomDropdown), new PropertyMetadata(false));
}
Xaml :
<Style TargetType="{x:Type local:CustomDropdown}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<ToggleButton IsChecked="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Popup StaysOpen="False"
Placement="Bottom"
IsOpen="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<Border Background="White"
BorderBrush="Black"
BorderThickness="1"
Padding="50">
<TextBlock Text="Popup!" />
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
If you want it to work as expected with ClickMode.Press, you should programmatically set the IsOpen property to false whenever you want to the close the Popup. For example whenever you detect a click outside of the ToggleButton.
You could for example handle the PreviewMouseLeftButtonDown event for the parent window in your control. Something like this:
internal class CustomDropdown : ContentControl
{
private ToggleButton _toggleButton;
public CustomDropdown()
{
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_toggleButton = GetTemplateChild("toggleButton") as ToggleButton;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(this);
window.PreviewMouseLeftButtonDown += OnWindowPreviewMouseLeftButtonDown;
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(this);
window.PreviewMouseLeftButtonDown -= OnWindowPreviewMouseLeftButtonDown;
}
private void OnWindowPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ToggleButton toggleButton = FindParent<ToggleButton>(e.OriginalSource as DependencyObject);
if (toggleButton != _toggleButton)
IsOpen = false;
}
private static T FindParent<T>(DependencyObject dependencyObject) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null)
return null;
var parentT = parent as T;
return parentT ?? FindParent<T>(parent);
}
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(CustomDropdown), new PropertyMetadata(false));
}
}
XAML:
<ControlTemplate>
<Grid>
<ToggleButton x:Name="toggleButton" ...
You already have a working answer. However, finding the parent Window and parent ToggleButton can impact performance (depending on the depth of the visual tree).
As an alternative solution I suggest to focus on handling the Popup instead.
There are two conditions that prevent the Popup from closing itself: the button is configured with the ButtonBase.ClickMode set to ClickMode.Pressed AND the user is not clicking anything focusable inside the Popup.
If one of those two conditions evaluates to false (=> ClickMode.Release or the user has moved focus inside the Popup) your code will work as you would expected it to work.
Note that in order to allow the user to move focus inside the Popup, there must be a child that is focusable (UIElement.Focusable is set to true - it's false by default for most controls that don't require user interaction). For example, TextBlock is not focusable by default.
Because you want to keep the button configured to raise the Click event on mouse button press, you have to move the focus manually. But when you set it manually, the Popup won't receive a mouse click to setup itself to watch the focus. Therefore, you will end up closing the Popup manually (taking away the related control from the Popup).
The following example closes the Popup by observing the Mouse.PreviewMouseDownOutsideCapturedElement event to identify when the focus has moved away from the CustomDropdown control (mouse click outside the Popup):
CustomDropdown.cs
internal class CustomDropdown : ContentControl
{
public bool IsOpen
{
get => (bool)GetValue(IsOpenProperty);
set => SetValue(IsOpenProperty, value);
}
public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
"IsOpen",
typeof(bool),
typeof(CustomDropdown),
new PropertyMetadata(default(bool), OnIsOpenChanged));
public CustomDropdown()
{
Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OnPreviewMouseDownOutsideCapturedElement);
}
private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool isOpen = (bool)e.NewValue;
if (isOpen)
{
_ = Mouse.Capture(d as IInputElement, CaptureMode.SubTree);
}
else
{
_ = Mouse.Capture(null);
}
}
// Manually close the Popup if click is recorded outside the CustomDropdown/Popup
private void OnPreviewMouseDownOutsideCapturedElement(object sender, MouseButtonEventArgs e)
{
SetCurrentValue(IsOpenProperty, false);
}
}
I'm don't know how to get focused item automatically change within a ListView.
I would like the focused item in the view to automatically change when I change the "IsSelected" property to an other element in the databinded list:
When an item is modified by PC/SC card reader (see this as input), the next element should be focused like this:
I would like to stay in MVVM and therefor not having View referenced in the ViewModel. Below is my current code.
Model : The main purpose is to extend a DTO with an IsSelected property and implementing INotifyPropertyChanged
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsSelected;
private DtoReader _dtoReader;
public SmartDeviceModel(DtoReader _reader)
{
_dtoReader = _reader;
}
public string DisplayName => _dtoReader.DisplayName;
public string Uid
{
get
{
return _dtoReader.Uid;
}
set
{
_dtoReader.Uid = value;
OnPropertyChanged("Uid");
}
}
public long RadioId
{
get
{
return _dtoReader.RadioId : _dtoMarker.RadioId;
}
set
{
_dtoReader.RadioId = value;
OnPropertyChanged("RadioId");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
ViewModel received events of a PC/SC card reader to pair data from RFID chip with current selected item. When RFID chip is removed from PC/SC Reader, the next element is well selected but got not focused.
public class ScanDeviceViewModel : BaseViewModel
{
public BindingList<SmartDeviceModel> ReaderList { get; }
public int SelectedReaderIndex;
private ITagReaderInput _rfidReader;
public ScanDeviceViewModel()
{
//Get Data listener for RFID Tag
_rfidReader = new IdentivTagReader.IdentivTagReader();
// Data Source of DTO
SiteInteractor siteInterractor = new SiteInteractor();
// List used for DataBinding
ReaderList = new BindingList<SmartDeviceModel>();
foreach (DtoReader m in SiteInteractor.GetReaders().OrderBy(x => x.DisplayName))
{
ReaderList.Add(new SmartDeviceModel(m));
}
if (ReaderList.Count() > 0)
{
for (var i = 0; i < ReaderList.Count(); i++)
{
if (String.IsNullOrEmpty(ReaderList[i].Uid))
{
SelectedReaderIndex = i;
ReaderList[i].IsSelected = true;
break;
}
}
}
_rfidReader.LabelDetected += RfidTagDetected;
_rfidReader.LabelRemoved += RfidRemoved;
}
private void RfidTagDetected(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex] != null && string.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid))
{
ReaderList[SelectedReaderIndex].IsSelected = true;
ReaderList[SelectedReaderIndex].Uid = tag.Uid;
ReaderList[SelectedReaderIndex].RadioId = tag.RadioId;
}
}
private void RfidRemoved(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex].Uid == tag.Uid)
{
ReaderList[SelectedReaderIndex].IsSelected = false;
while (ReaderList.Count >= SelectedReaderIndex + 1)
{
SelectedReaderIndex++;
if (String.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid)){
ReaderList[SelectedReaderIndex].IsSelected = true;
break;
}
}
}
}
}
View I'm using a "Setter" using databinding to my model property "IsSelected" as suggested here but I most missed something else I don't understand yet.
<ListView ItemsSource="{Binding ReaderList}"
Margin="5" x:Name="listViewReader" SelectionMode="Single"
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderBrush" Value="LightGray" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Viewbox Grid.Row ="0" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Bottom" MaxHeight="90">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="{Binding DisplayName}" />
<DockPanel Grid.Row="1">
<Label Content="UID"/>
<Label Content="{Binding Uid}"/>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="1">
<Label Content="RadioID" />
<Label Content="{Binding RadioId}"/>
</DockPanel>
</Grid>
</Viewbox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I tried several approach like this answer, although item is well selected, it is not focused.
I finally figure it out. Below is my current working code.
In the Model I have just changed the flag IsSelected to IsCurrent to avoid confusion with ListViewItem built-in property but it might just be an implementation detail.
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsCurrent;
[...]
}
The BindingList in ViewModel is mostly the same as in OP:
public class ScanDeviceViewModel : INotifyPropertyChanged
{
public BindingList<SmartDeviceModel> ReaderList { get; internal set; }
[...]
}
NB : BindingList seems to reduce OnNotifyPropertyChange need but other Type of List should work with a tiny bit of extra code. I also noticed BindingList might not be suited for huge list scenario.
The View is then using the above ViewModel as DataContext and therefore Binding ItemSource to the BindingList. The ListViewItem Style Setter is then using the IsCurrent Property from the Model.
<ListView ItemsSource="{Binding ReaderList}"
SelectionMode="Single"
SelectionChanged="OnListViewSelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsCurrent}" />
</Style>
</ListView.ItemContainerStyle>
[...]
And finally this piece of View Code behind below is mainly to simulate the focus as per user input, otherwise the elemant get selected but not focused and might be outside the visible item scope :
private void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView listView = e.Source as ListView;
if (listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) is FrameworkElement container)
{
container.Focus();
}
}
According to MVVM you can implement custom Interaction Behavior:
Import to XAML: xmlns:b="http://schemas.microsoft.com/xaml/behaviors" (if you are using .NET Core 3.1 - 5)
Add to content-body:
<ListView ...>
<b:Interaction.Behaviors>
<local:AutoScrollToLastItemBehavior />
</b:Interaction.Behaviors>
</ListView>
Finally add the next class:
public sealed class AutoScrollToLastItemBehavior : Microsoft.Xaml.Behaviors.Behavior<ListView>
{
// Need to track whether we've attached to the collection changed event
bool _collectionChangedSubscribed = false;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += SelectionChanged;
// The ItemSource of the listView will not be set yet,
// so get a method that we can hook up to later
AssociatedObject.DataContextChanged += DataContextChanged;
}
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView();
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ScrollIntoView();
}
private void DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The ObservableCollection implements the INotifyCollectionChanged interface
// However, if this is bound to something that doesn't then just don't hook the event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && !_collectionChangedSubscribed)
{
// The data context has been changed, so now hook
// into the collection changed event
collection.CollectionChanged += CollectionChanged;
_collectionChangedSubscribed = true;
}
}
private void ScrollIntoView()
{
int count = AssociatedObject.Items.Count;
if (count > 0)
{
var last = AssociatedObject.Items[count - 1];
AssociatedObject.ScrollIntoView(last);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= SelectionChanged;
AssociatedObject.DataContextChanged -= DataContextChanged;
// Detach from the collection changed event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && _collectionChangedSubscribed)
{
collection.CollectionChanged -= CollectionChanged;
_collectionChangedSubscribed = false;
}
}
}
I want to bind a focus behavior to a reset button that will put the focus on the control named in the ElementToFocus property
<Style TargetType="Button" x:Key="Button_Reset" BasedOn="{StaticResource Button_Default}" >
<Setter Property="ElementToFocus" />
<Setter Property="behaviors:EventFocusAttachment.ElementToFocus" Value="{Binding ElementName=ElementToFocus}" />
</Style>
Control Markup:
<Button
x:Name="button_Clear"
Style="{DynamicResource Button_Reset}"
HorizontalAlignment="Right"
Content="Clear"
Command="{Binding Path=ClearCommand}"
ElementToFocus="textbox_SearchText"
Margin="0,0,0,7" />
How can I accomplish this?
I have created an attached behavior to try and achieve what you are trying to do.
Attached Behavior Code:
public static class ElementFocusBehavior
{
public static readonly DependencyProperty ElementToFocusProperty =
DependencyProperty.RegisterAttached("ElementToFocus", typeof (FrameworkElement), typeof (ElementFocusBehavior), new PropertyMetadata(default(FrameworkElement), PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var button = dependencyObject as Button;
if (button == null) return;
if (button.IsLoaded)
{
AddClickHandler(button);
}
else
{
button.Loaded += ButtonOnLoaded;
}
}
private static void ButtonOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var button = (Button) sender;
button.Loaded -= ButtonOnLoaded;
AddClickHandler(button);
}
static void AddClickHandler(Button button)
{
button.Click += ButtonOnClick;
}
private static void ButtonOnClick(object sender, RoutedEventArgs routedEventArgs)
{
var fe = GetElementToFocus(sender as Button) as FrameworkElement;
if (fe == null) return;
fe.Focus();
}
public static void SetElementToFocus(Button button, FrameworkElement value)
{
button.SetValue(ElementToFocusProperty, value);
}
public static FrameworkElement GetElementToFocus(Button button)
{
return (FrameworkElement) button.GetValue(ElementToFocusProperty);
}
}
And the XAML for Button:
<Button Content="Reset" local:ElementFocusBehavior.ElementToFocus="{Binding ElementName=TextBoxThree, Path=.}" />
Sample code from my MainWindow:
<StackPanel>
<TextBox Name="TextBoxOne" />
<TextBox Name="TextBoxTwo" />
<TextBox Name="TextBoxThree" />
<Button Content="Reset" local:ElementFocusBehavior.ElementToFocus="{Binding ElementName=TextBoxThree, Path=.}" />
</StackPanel>
Basically, what I did was,
have an attached behavior to store the element to be focused,
and then in the attached behavior add event handler to button Click event,
in the Click event set the Focus on the ElementToFocus element
Hope this helps.
I have a Button containing a Hyperlink, like so:
<Button IsEnabled="False">
<Hyperlink IsEnabled="True">Testing</Hyperlink>
</Button>
I need the Hyperlink to be enabled, however the Button to be disabled. How can I achieve this?
The above simply results in both controls being disabled.
I solved this problem by creating a simple wrapper element that breaks the IsEnabled inheritance chain from the parent.
The framework's default coerce callback checks the parent IsEnabled value and inherits it. This control sets a new coerce callback that just returns the value directly without checking inheritance.
public class ResetIsEnabled : ContentControl
{
static ResetIsEnabled()
{
IsEnabledProperty.OverrideMetadata(
typeof(ResetIsEnabled),
new UIPropertyMetadata(
defaultValue: true,
propertyChangedCallback: (_, __) => { },
coerceValueCallback: (_, x) => x));
}
}
In the example from the question it would be used like this:
<Button IsEnabled="False">
<ResetIsEnabled>
<!-- Child elements within ResetIsEnabled have IsEnabled set to true (the default value) -->
<Hyperlink>Testing</Hyperlink>
</ResetIsEnabled>
</Button>
Control Hyperlink has strangely with the property IsEnabled. In addition to the one that you mentioned, namely, the full value inheritance from a parent, there is another similar.
Hyperlink for the specific control, which has been turned off (IsEnabled="False"), setting (IsEnabled="True") will not update the Hyperlink property. The solution - use a relative source for Hyperlink (more info).
For solving your question, I have decided that it is not the standard way to solve. So I created a Class with its own dependencies properties. It has it's property MyIsEnabled and MyStyle. As you might guess from the title, the first sets its property IsEnabled and MyStyle need to specify the button style, simulating the IsEnabled="False" behavior.
SimulateDisable Style
<Style x:Key="SimulateDisable" TargetType="{x:Type Button}">
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Background" Value="Gainsboro" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="4" BorderThickness="1" BorderBrush="DarkBlue" SnapsToDevicePixels="True">
<ContentPresenter x:Name="MyContentPresenter" Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Define Button with yours properties:
<Button Name="MyButton" local:MyClass.MyIsEnabled="False" local:MyClass.MyStyle="{StaticResource SimulateDisable}" Width="100" Height="30" Click="Button_Click">
<Hyperlink IsEnabled="True" Click="Hyperlink_Click">Testing</Hyperlink>
</Button>
Listing of MyClass
public class MyClass : DependencyObject
{
public static readonly DependencyProperty MyIsEnabledProperty;
public static readonly DependencyProperty MyStyleProperty;
#region MyIsEnabled
public static void SetMyIsEnabled(DependencyObject DepObject, bool value)
{
DepObject.SetValue(MyIsEnabledProperty, value);
}
public static bool GetMyIsEnabled(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(MyIsEnabledProperty);
}
#endregion MyIsEnabled
#region MyStyle
public static void SetMyStyle(DependencyObject DepObject, Style value)
{
DepObject.SetValue(MyStyleProperty, value);
}
public static Style GetMyStyle(DependencyObject DepObject)
{
return (Style)DepObject.GetValue(MyStyleProperty);
}
#endregion MyStyle
static MyClass()
{
MyIsEnabledProperty = DependencyProperty.RegisterAttached("MyIsEnabled",
typeof(bool),
typeof(MyClass),
new UIPropertyMetadata(false, OnPropertyChanged));
MyStyleProperty = DependencyProperty.RegisterAttached("MyStyle",
typeof(Style),
typeof(MyClass),
new UIPropertyMetadata(OnPropertyChanged));
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Button MyButton = sender as Button;
bool MyBool = GetMyIsEnabled(MyButton);
if (MyBool == false)
{
MyButton.Style = MyClass.GetMyStyle(MyButton);
}
}
}
Plus for the event Hyperlink pointing e.Handled = true, so that the event did not happen next.
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hyperlink Click!");
e.Handled = true;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button Click! Don't show it's!");
}
Output
P.S. Sorry for late answer :).