A command from a context menu that does not work - c#

Well, I have a WPF project and I'm using Visual Studio 2010. I'm using C# and XAML, and I'm using the MVVM pattern.
The problem I have must be simple but I just can't see why it's not working.
Alright, so I have a project with a ListBox. In that ListBox are many ChatNodes; each represented visually. The visual element for a ChatNode is here:
<ControlTemplate x:Key="NodeVisualTemplate">
<Grid>
<Border BorderThickness="2" Margin="2" CornerRadius="5,5,5,5" BorderBrush="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.SelectionMode, Converter={StaticResource SelectionModeToColourConverter}}" ContextMenu="{StaticResource ChatNodeMenu}">
<StackPanel Opacity="{Binding IsInvisibleNode, Converter={StaticResource ResourceKey=VisibleToOpacityConverter}}">
<TextBlock Text="Test" Background="AntiqueWhite"/>
<TextBlock Text="{Binding Path=NodeText}" Background="Aqua"/>
<StackPanel Orientation="Horizontal">
<TextBox Text="Type here" MinWidth="50"/>
<Image Source="{StaticResource ImgFolder}" Margin="0,0,5,0" Width="32" Height="32"/>
</StackPanel>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
I draw your attention to the BorderBrush for the Border. I will show that again here:
BorderBrush="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.SelectionMode, Converter={StaticResource SelectionModeToColourConverter}}"
This code sits inside a ListBoxItem and is able to find the parent ListBox and then access a property of it. This works fine. The property is in a view model called ChatNodeListViewModel and looks like this:
private int _selectionMode = 0;
public int SelectionMode
{
get { return _selectionMode; }
set
{
if (_selectionMode != value)
{
_selectionMode = value;
RaisePropertyChanged("SelectionMode");
}
}
}
I mention it specifically because another thing which is almost identical is not working, even though this BorderBrush code is working.
So, on to the not working part.
In that same ControlTemplate above, we see a ContextMenu, called 'ChatNodeMenu'. This is as follows:
<ContextMenu x:Key="ChatNodeMenu" >
<MenuItem Header="Remove" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.RemoveChatNodeCommand}" />
</ContextMenu>
It has the same binding, only this time it is for a Command called 'RemoveChatNodeCommand'. The menu does appear on a right click on a ChatNode, but the command does not run. I have actually used almost identical code in other parts of my project so I assume it's correct... but clearly there is an error somewhere.
So where is this command? It is in the view model called 'ChatNodeListViewModel' and I will present it here:
void RemoveChatNodeExecute()
{
MessageBox.Show("Remove chat node");
return;
}
bool CanRemoveChatNode()
{
return true;
}
public ICommand RemoveChatNodeCommand { get { return new RelayCommand(RemoveChatNodeExecute, CanRemoveChatNode); } }
I have also used this in many places throughout my code and it's worked every time but this one.
So, either there's a fault in the code or perhaps a simple mistake. I've checked the name of the command and re-copied it several times. I've checked the other parts of my code where I've used the same code, but I can't see anything wrong. I've cleaned and rebuilt my project just in case.
If anyone can venture a guess, I would be very happy with that.

The ListBox is not a visual ancestor of a ContextMenu but you could set the Tag property of the Border to the ListBox and then bind to the PlacementTarget of the ContextMenu:
<Border ... Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}" ContextMenu="{StaticResource ChatNodeMenu}">
<MenuItem Header="Remove" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag.DataContext.RemoveChatNodeCommand}" />

I have same issue related to conextmenu not able to work on command. could you help me to find solution.
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="test2">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="rr2" Command="{Binding DataContext.NextCommand, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" CommandParameter="{Binding}" ></MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>

Related

MAUI ListView gets app to crash/is buggy on Android when scrolling

I am developing the prototype for a mobile application in MAUI, just for Android right now. The ListView is not working as expected for me though. Everything is fine when the number of items fit the screen, but the moment there is scrolling involved, the app crashes. The underlying Android error seems to be:
java.lang.IllegalStateException: The specified child already has a parent.
If I scroll very slowly, the issue does not seem to occur. Here's the ListView:
<ListView ItemsSource="{Binding Devices}"
Style="{StaticResource ListViewSimpleStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:DataType="local:DeviceEntryModel">
<RadioButton IsChecked="{Binding IsChecked, Mode=TwoWay}"
GroupName="Devices">
<RadioButton.Content>
<Label BindingContext="{Binding Source={RelativeSource AncestorType={x:Type RadioButton}}, Path=BindingContext}"
Text="{Binding Name}"
IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type RadioButton}}, Path=IsEnabled}" />
</RadioButton.Content>
</RadioButton>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The item source is an ObservableCollection<DeviceEntry>:
public partial class DeviceEntryModel : ObservableObject
{
public DeviceEntry DeviceEntry { get; init; }
public string Name { get => DeviceEntry.Name; }
public string Address { get => DeviceEntry.Address; }
[ObservableProperty]
bool isChecked;
}
What am I doing wrong?
One thing I have tried that does fix the crashing is to set the ListView CachingStrategy to "RecycleElementAndDataTemplate". The problem with that is, if a radio button is checked and goes out of view while scrolling, it loses its state and becomes unchecked. There are also various other bugs, like the list item text disappearing.
I fixed all the issues, wrap the whole ListView XAML in a ScrollView.
<ScrollView>
<ListView ItemsSource="{Binding Devices}"
Style="{StaticResource ListViewSimpleStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:DataType="local:DeviceEntryModel">
<RadioButton IsChecked="{Binding IsChecked, Mode=TwoWay}"
GroupName="Devices">
<RadioButton.Content>
<Label BindingContext="{Binding Source={RelativeSource AncestorType={x:Type RadioButton}}, Path=BindingContext}"
Text="{Binding Name}"
IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type RadioButton}}, Path=IsEnabled}" />
</RadioButton.Content>
</RadioButton>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollView>
#something5143
I don't known the solution but on the .NET MAUI documentation
Warning
ScrollView objects should not be nested. In addition, ScrollView
objects should not be nested with other controls that provide
scrolling, such as CollectionView, ListView, and WebView.

Binding by relative source is not working in Tooltip WPF

I am using Syncfusion visual style. I am trying to bind the ToolTip foreground inside the TextBlock present inside the StackPanel for displaying the ToolTip text.But the binding doesn't work properly in the TextBlock.
MainWindow.xaml
<Grid>
<CheckBox Grid.Row="5" Grid.Column="1" HorizontalAlignment="Center" Margin="0,4,10,0" VerticalAlignment="Center" IsChecked="{Binding AutoAdd, Mode=TwoWay}">
<CheckBox.ToolTip>
<StackPanel>
<TextBlock Foreground="{Binding Path=Foreground, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}" FontWeight="Bold" FontSize="14" Margin="0,0,0,5">Automatically Add To Path</TextBlock>
<TextBlock Foreground="{Binding Path=Foreground, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}">
If this path is associated with the primary configuration,
<LineBreak />
automatically add newly instantiated optical elements to the end of the path.
</TextBlock>
<Border BorderBrush="Silver" BorderThickness="0,1,0,0" Margin="0,8" />
<WrapPanel>
<Image Margin="0,0,5,0" />
<TextBlock Foreground="{Binding Path=Foreground, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}" FontStyle="Italic">Press F1 for more help</TextBlock>
</WrapPanel>
</StackPanel>
</CheckBox.ToolTip>
</CheckBox>
</Grid>
Any other work around regarding this issue.
Regards,
Hari Prasad
A Tooltip is in a different datacontext and not directly in the visual tree to a control which appears to be it's parent. Hence, when you search up the visual tree using relativesouce, it will find nothing.
You may use PlacementTarget to reference the thing which is nominally it's parent - checkbox in your markup
"{Binding Path=PlacementTarget.PropertyName, RelativeSource={RelativeSource Self}}"
Here PropertyName is whichever property you want.
That could be DataContext.ViewModelProperty if you wanted a property in your viewmodel.
In this instance I would try
Foreground="{Binding Path=PlacementTarget.Foreground, RelativeSource={RelativeSource Self}}"
Depending on your usage, maybe using a dynamicresource would be simpler though.

C# MVVM: MenuItem Command don´t execute in ViewModel

I created a Delete MenuItem and binding a command to it.
Now I have the problem, if I am pressing the Delete MenuItem, nothing happens. Also if the program is executed with the debugger, it never reaches the private void DeleteItem.
xaml:
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="#F5F5F5" Width="80" Height="60" Margin="0,5,5,5">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding Path=DeleteItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType= MenuItem}}">
<MenuItem.Icon>
<Label FontFamily="#FontAwesome" Content="" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Border.ContextMenu>
ViewModel:
public ICommand DeleteItemCommand { get; set; }
DeleteItemCommand = new RelayCommand(DeleteItem);
private void DeleteItem(object obj)
{
try
{
// Do Magic
}
catch (Exception)
{
MessageBox.Show(error);
}
}
Would be great, if someone could help me or have any ideas how to solve it, because i can´t find the error.
ContextMenu is indeed not part of the visual tree. But I think because of that reason RelativeSource will not work because the binding will look up to the visual tree for the datacontext. contextMenu is not part of that visual tree so it will not find the proper datacontext. I've found a solution for this in the past using a proxy element in the resources for the window. Then set this proxyelement as content for a hidden contentcontrol in the window. On the menuItem set the CommandBinding to Datacontext.DeleteCommand and the soure to the static resource proxyelement. It is a bit hackish, but it works.
So to show some xaml, try this:
First in the resources of the window create a frameworkelement with the datacontext set to the windows datacontext (the viewmodel)
<Window.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
</Window.Resources>
Then use the resource for the content of a collapsed content control. And set the proper binding to the menuItem. Something like this:
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>
<ListBox x:Name="lbTest" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="#F5F5F5" Width="80" Height="60" Margin="0,5,5,5">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding DataContext.DeleteCommand, Source={StaticResource ProxyElement}}">
<MenuItem.Icon>
<Label FontFamily="#FontAwesome" Content="" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Border.ContextMenu>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This should work. Give it a try.
Not sure if this will help but try using Binding instead of Binding Path.
ContextMenu is not part of VisualTree, that's why the binding fails. You can use some kind of relay like ContextMenu.PlacementTarget.Tag.Property as a cache for the second trail of binding search.
<ContextMenu>
<MenuItem Command="my:ImgTreeView.Folders" Header="Folders"
IsEnabled="{Binding Path=PlacementTarget.Tag.IsCheckFolder, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="StarFolders.png" />
</MenuItem.Icon>
</MenuItem>
<!-- ... -->
</ContextMenu>

Getting ContextMenu target from Item in ItemsControl

I have an ItemsControl bound to a collection on my ViewModel. This ItemsControl presents several "Messages". I need a ContextMenu that, when clicked, provides an option to Copy that particular message to the clipboard.
The Command should activate on the ViewModel and the CommandParameter I want to pass is the Message itself.
The problem I'm having is getting the actual message that the ContextMenu has been opened OVER.
I've tried mutliple different approaches, but I cannot seem to figure out a way to pass the message as the Command's parameter.
Should I look for a different approach to accomplish this? Is the issue using an ItemsControl with an ItemsPresenter?
<ScrollViewer CanContentScroll="False"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Grid.RowSpan="1">
<ItemsControl ItemsSource="{Binding MyActiveConversation.OrderedMessages, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding MyActiveConversation.IsOptOut, Converter={StaticResource BoolToEnabledInverter}}"
ItemTemplateSelector="{StaticResource tSelector}"
VirtualizingPanel.IsVirtualizing="False"
SourceUpdated="SourceUpdatedHandler" MinWidth="450">
<ItemsControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy" Template="{DynamicResource MenuItemControlTemplate}"
CommandParameter="{Binding}" Command="{Binding Path=Data.CopyTextMessageCommand, Source={StaticResource ContextProxy}}">
</MenuItem>
</ContextMenu>
</ItemsControl.ContextMenu>
<ItemsControl.Template>
<ControlTemplate>
<Grid>
<ItemsPresenter VirtualizingPanel.IsVirtualizing="False"/>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
CommandParameter="{Binding}" works good for me, same for CommandParameter="{Binding .}"

How to Interact with 2 or More ViewModels

I have written a tool in which a ListBox is bound to a ObserservableCollection<object> with varying datatypes I've define. I use a PropertyDataTemplateSelector to present the data in the ListBox. The PropertyDataTemplateSelector references several DataTemplates that are set as UserControls. There is a background class that provides logic to the PropertyDataTemplateSelector by checking the object type and then applying the correct DataTemplate.
Here's an abbreviated example of the XAML for the UserControls and the MainWindow.
UserControl1
<TextBlock Text="{Binding Path=Val1}"
Style="{StaticResourse Yes}" />
<Button Content="I'm Button 1"
Command="{Binding Path=PathtoCommand1}"
CommandParameter="{Binding Parameter1}"
IsEnabled="{Binding IsEnabled1}" />
<Button Content="I'm Button 2"
Command="{Binding Path=PathtoCommand2}"
CommandParameter="{Binding Parameter2}"
IsEnabled="{Binding IsEnabled2}"
Tag="{Binding Path="DataContext.TagItem2}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem IsCheckable="True"
IsChecked="{Binding Path=Tag}"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</UserControl>
UserControlN
<UserControl x:Class="AwesomerControl">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=FancyName2}"
Style="{StaticResourse Yes}" />
<Button Content="Clicker 1"
Command="{Binding Path=DoSomethingGreat1}"
CommandParameter="{Binding Greatness1}"
IsEnabled="{Binding IsTurnedOn1}" />
<Button Content="Clicker 2"
Command="{Binding Path=DoSomethingGreat2}"
CommandParameter="{Binding Greaterness2}"
IsEnabled="{Binding IsTurnedOn2}"
Tag="{Binding Path="DataContext.TagItem2}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem IsCheckable="True"
IsChecked="{Binding Path=Tag}"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</UserControl>
Here I set the UserControls to a specified DataTemplate. The UserControls were moved out to make the XAML easier to read/navigate. In actuality the UserControls are a few hundred lines each.
<Window.Resources>
<DataTemplate x:Key"Template1">
<customControls:AwesomeControl/>
</DataTemplate>
...
<DataTemplate x:Key"TemplateN">
<customControls:AwesomerControl/>
</DataTemplate>
<dts:PropertyDataTemplateSelector x:Key="templateselector"
Template1="{StaticResource Template1"}
...
TemplateN="{StaticResource TemplateN"}
</Window.Resources>
The ListBox is defined as this.
<ListBox ItemSource="{Binding Path=CollectionofMyObjects}"
ItemTemplateSelector="{StaticResource templateselector}" />
I am using a single ViewModel to drive the MainWindow and the UserControls.
So that's where I'm at, essentially. I have this currently working as I'd like, but in an ongoing effort to learn (this is my first MVVM/WPF/C# project) I'd like to keep exploring how to make my code "better" (however that's defined). I'm not looking to solve an error here. So to avoid a general/broad question, I'll ask what I think I want to know. Someone can correct me and I'll update the "question(s)" appropriately
Question: How can I go about producing a ViewModel for each of the UserControls? Some of the ViewModels, for the UserControls, will occasionally require two-way communication to the MainWindow_ViewModel. The main crux of my problem is figuring out how the multiple VMs will communicate.
You're close, but it's not quite MVVM yet. ;)
First, break out all the functionality that is relevant to each UserControl into their own classes. These are your view-model classes.
Your controls should now become "view" classes, and they deserve their own mark-up file. Rather than use a template selector, you can use the DataTemplate.DataType to automatically connect the view-model class type to its view.
There are a lot of options for communication between view-models. To further your education, I'd consider looking at a light-weight MVVM framework that has built-in solutions for communication. My personal favorite is Caliburn.Micro, which includes an EventAggregator, a service that provides the ability to publish an object from one view-model to another in a loosely-coupled fashion.
Keep learning, you're on the right track!

Categories