Я хотів би вибрати WPF TreeView Node клацанням правою кнопкою миші, безпосередньо перед відображенням ContextMenu.
Для WinForms я можу використовувати такий код, як цей вузол Find, натиснутий у контекстному меню , які альтернативи WPF?
Я хотів би вибрати WPF TreeView Node клацанням правою кнопкою миші, безпосередньо перед відображенням ContextMenu.
Для WinForms я можу використовувати такий код, як цей вузол Find, натиснутий у контекстному меню , які альтернативи WPF?
Відповіді:
Залежно від способу заселення дерева значення відправника та e.Source можуть відрізнятися .
Одне з можливих рішень - використовувати e.OriginalSource та знайти TreeViewItem за допомогою VisualTreeHelper:
private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);
if (treeViewItem != null)
{
treeViewItem.Focus();
e.Handled = true;
}
}
static TreeViewItem VisualUpwardSearch(DependencyObject source)
{
while (source != null && !(source is TreeViewItem))
source = VisualTreeHelper.GetParent(source);
return source as TreeViewItem;
}
if (treeViewItem == null) treeView.SelectedIndex = -1
або treeView.SelectedItem = null
. Я вважаю, що або треба працювати.
Якщо ви хочете вирішити лише XAML, ви можете використовувати Blend Interactivity.
Припустимо, TreeView
це дані, пов'язані з ієрархічною колекцією моделей перегляду, що мають Boolean
властивість IsSelected
та String
властивість Name
, а також колекцією дочірніх елементів Children
.
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<ei:ChangePropertyAction PropertyName="IsSelected" Value="true" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Є дві цікаві частини:
The TreeViewItem.IsSelected
Властивість прив'язаний до IsSelected
власності на вигляд-моделі. Якщо встановити IsSelected
властивість на вигляд-моделі на істинне, вибере відповідний вузол у дереві.
При PreviewMouseRightButtonDown
пожежі на візуальній частині вузла (у цьому зразку а TextBlock
) IsSelected
властивість у вигляд-моделі встановлюється на істину. Повернувшись до 1., можна побачити, що відповідний вузол, на який було натиснуто дерево, стає вибраним вузлом.
Один із способів отримати суміш інтерактивності у своєму проекті - використовувати пакет NuGet Unofficial.Blend.Interactivity .
i
і ei
відображення простору імен, хоч і в яких збірках їх можна знайти. Я припускаю: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
і xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
, які знаходяться в зборах System.Windows.Interactivity та Microsoft.Expression.Interactions відповідно.
ChangePropertyAction
це намагається встановити IsSelected
властивість зв'язаного об'єкта даних, який не є частиною інтерфейсу користувача, тому він не має IsSelected
властивості. Я щось роблю не так?
IsSelected
властивість, як зазначено у другому абзаці моєї відповіді: Припустимо, TreeView
це дані, пов'язані з ієрархічною колекцією моделей перегляду, що мають булеві властивостіIsSelected
... (мій акцент).
Використання "item.Focus ();" не працює на 100%, використовуючи "item.IsSelected = true;" робить.
У XAML додайте обробник PreviewMouseRightButtonDown у XAML:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
</Style>
</TreeView.ItemContainerStyle>
Потім обробіть подію так:
private void TreeViewItem_PreviewMouseRightButtonDown( object sender, MouseEventArgs e )
{
TreeViewItem item = sender as TreeViewItem;
if ( item != null )
{
item.Focus( );
e.Handled = true;
}
}
Використовуючи оригінальну ідею від alex2k8, правильну обробку невізуальних засобів від Wieser Software Ltd, XAML від Stefan, IsSelected from Erlend, і мій внесок у справжнє створення статичного методу Generic:
XAML:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- We have to select the item which is right-clicked on -->
<EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown"
Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
</Style>
</TreeView.ItemContainerStyle>
C # код позаду:
void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem =
VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject);
if(treeViewItem != null)
{
treeViewItem.IsSelected = true;
e.Handled = true;
}
}
static T VisualUpwardSearch<T>(DependencyObject source) where T : DependencyObject
{
DependencyObject returnVal = source;
while(returnVal != null && !(returnVal is T))
{
DependencyObject tempReturnVal = null;
if(returnVal is Visual || returnVal is Visual3D)
{
tempReturnVal = VisualTreeHelper.GetParent(returnVal);
}
if(tempReturnVal == null)
{
returnVal = LogicalTreeHelper.GetParent(returnVal);
}
else returnVal = tempReturnVal;
}
return returnVal as T;
}
Редагувати: Попередній код завжди працював нормально для цього сценарію, але в іншому сценарії VisualTreeHelper.GetParent повернув нульове значення, коли LogicalTreeHelper повернув значення, так виправив це.
Практично правильно , але потрібно стежити за невидимими зображеннями на дереві (наприклад Run
, наприклад).
static DependencyObject VisualUpwardSearch<T>(DependencyObject source)
{
while (source != null && source.GetType() != typeof(T))
{
if (source is Visual || source is Visual3D)
{
source = VisualTreeHelper.GetParent(source);
}
else
{
source = LogicalTreeHelper.GetParent(source);
}
}
return source;
}
Я думаю, що реєстрація обробника класу повинна зробити свою справу. Просто зареєструйте маршрутизований обробник подій у TreeViewItem PreviewMouseRightButtonDownEvent у вашому файлі коду app.xaml.cs таким чином:
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(TreeViewItem), TreeViewItem.PreviewMouseRightButtonDownEvent, new RoutedEventHandler(TreeViewItem_PreviewMouseRightButtonDownEvent));
base.OnStartup(e);
}
private void TreeViewItem_PreviewMouseRightButtonDownEvent(object sender, RoutedEventArgs e)
{
(sender as TreeViewItem).IsSelected = true;
}
}
Ще один спосіб вирішити це за допомогою MVVM - це команда прив’язки для клацання правою кнопкою миші на вашій моделі перегляду. Там же можна вказати і іншу логіку source.IsSelected = true
. Для цього використовується лише xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
з System.Windows.Interactivity
.
XAML для перегляду:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseRightButtonDown">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.TreeViewItemRigthClickCommand}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Переглянути модель:
public ICommand TreeViewItemRigthClickCommand
{
get
{
if (_treeViewItemRigthClickCommand == null)
{
_treeViewItemRigthClickCommand = new RelayCommand<object>(TreeViewItemRigthClick);
}
return _treeViewItemRigthClickCommand;
}
}
private RelayCommand<object> _treeViewItemRigthClickCommand;
private void TreeViewItemRigthClick(object sourceItem)
{
if (sourceItem is Item)
{
(sourceItem as Item).IsSelected = true;
}
}
У мене виникли проблеми з вибором дітей методом HierarchicalDataTemplate. Якби я вибрав дитину вузла, то якимось чином вибрав би головний батько цієї дитини. Я дізнався, що подія MouseRightButtonDown запрошуватиметься на кожному рівні дитини. Наприклад, якщо у вас є дерево на кшталт цього:
Пункт 1
- Дитина 1
- Дитина 2
- Підпункт1
- Підпункт2
Якщо я вибрав Subitem2, подія триватиме тричі, а пункт 1 буде обраний. Я вирішив це за допомогою булевого та асинхронного дзвінка.
private bool isFirstTime = false;
protected void TaskTreeView_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var item = sender as TreeViewItem;
if (item != null && isFirstTime == false)
{
item.Focus();
isFirstTime = true;
ResetRightClickAsync();
}
}
private async void ResetRightClickAsync()
{
isFirstTime = await SetFirstTimeToFalse();
}
private async Task<bool> SetFirstTimeToFalse()
{
return await Task.Factory.StartNew(() => { Thread.Sleep(3000); return false; });
}
Це відчувається трохи невдало, але в основному я встановив булеве значення true при першому проходженні через нього і змусив його скинутись на інший потік за кілька секунд (3 у цьому випадку). Це означає, що наступне проходження туди, де воно намагатиметься рухатися вгору, дерево буде пропущено, залишивши вас з обраним правильним вузлом. Здається, працює поки що :-)
MouseButtonEventArgs.Handled
на true
. Оскільки дитина є першою, яку викликають. Якщо встановити цю властивість на істинне, вимкніть інші виклики батьків.
Ви можете вибрати його за допомогою події вниз. Це призведе до вибору до початку контекстного меню.
Якщо ви хочете залишитися у межах MVVM, ви можете зробити наступне:
Вид:
<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
<TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Код позаду:
private void TreeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is TextBlock tb && tb.DataContext is YourTreeElementClass te)
{
trvName.Tag = te;
}
}
ViewModel:
private YourTreeElementClass _clickedTreeElement;
public YourTreeElementClass ClickedTreeElement
{
get => _clickedTreeElement;
set => SetProperty(ref _clickedTreeElement, value);
}
Тепер ви можете або реагувати на зміну властивості ClickedTreeElement, або можете скористатися командою, яка внутрішньо працює з ClickedTreeElement.
Розширений вид:
<UserControl ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<TreeView x:Name="trvName" ItemsSource="{Binding RootElementListView}" Tag="{Binding ClickedTreeElement, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<i:InvokeCommandAction Command="{Binding HandleRightClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:YourTreeElementClass}" ItemsSource="{Binding Path=Subreports}">
<TextBlock Text="{Binding YourTreeElementDisplayProperty}" PreviewMouseRightButtonDown="TreeView_PreviewMouseRightButtonDown"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</UserControl>