Command binding for button in ItemsControl in WPF NotifyIcon - c#

I'm using hardcodet's WPF NotifyIcon in my app, which is being implemented using MVVM Light. I'm trying to create a custom TrayPopup for the icon. In this popup I have an ItemsControl, whose ItemsSource is a list of objects "NumberList" in my ViewModel. For my DataTemplate I'm using a grid which display an property called "Id" of my source object, and a button. The button I'm trying to tie back to a RelayCommand in my ViewModel. I've done similar things with ItemsControls in the past by Binding using a RelativeSource to get the DataContext of the Window Ancestor. I tried a similar approach this time, but when I run the app, open the TrayPopup, and click the button nothing happens. My command is not fired in my ViewModel.
As a test I tried adding another button to my TrayPopup and binding its command to the same one in my ViewModel. That binding works fine, so it's not the command as far as I can tell.
Like I said, I've used this technique almost exactly in the past, just never before in a NotifyIcon. I've tried specifying the ElementName in the button Binding to the name of my Window, which didn't work. I've also tried changing the RelativeSource to the parent TaskbarIcon, also without success.
Here is an example implementation of what the NotifyIcon with the same problem:
<tb:TaskbarIcon PopupActivation="LeftOrRightClick">
<tb:TaskbarIcon.TrayPopup>
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0">
<ItemsControl ItemsSource="{Binding NumberList, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Id, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1" Content="Test" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.TestCommand}"/> <!--This binding doesn't work!-->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<Button Grid.Row="1" Content="Test2" Command="{Binding TestCommand}"/> <!--This works-->
</Grid>
</tb:TaskbarIcon.TrayPopup>
</tb:TaskbarIcon>
I'm fairly confident everything in the ViewModel is correct as I have been using a previous version of this app that I wrote a while ago. So far I haven't run in to any problems other than this NotifyIcon. Any ideas?

After banging away at it for a while I found an answer to my problem. The StackPanel in my ItemsPanelTemplate had the DataContext which contained the command I wanted to bind my button to, my main ViewModel. By specifying a name for the StackPanel, I was able to set the ElementName of my button to its name, and then the binding worked. Not sure why this method worked with the StackPanel and not the main Window though.
Anyway, here's the updated code if anyone runs in to this same problem:
<tb:TaskbarIcon PopupActivation="LeftOrRightClick">
<tb:TaskbarIcon.TrayPopup>
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0">
<ItemsControl ItemsSource="{Binding NumberList, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Id, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1" Content="Test" Command="{Binding ElementName=trayMainStack, Path=DataContext.TestCommand}"/> <!--Updated binding works!-->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="trayMainStack"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<Button Grid.Row="1" Content="Test2" Command="{Binding TestCommand}"/> <!--This works-->
</Grid>
</tb:TaskbarIcon.TrayPopup>
</tb:TaskbarIcon>

Related

How to get webview2 control in list control

I want to pass the webview2 control on the page to the ViewModel after clicking the button.
The following is part of the code for xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel>
<!--Click the button "Button_single", and the value of parameter can be obtained from ViewModel as Microsoft.Web.WebView2.Wpf.WebView2 type-->
<Button Command="{Binding BtnCommand1}" CommandParameter="{Binding ElementName=webView_single}">Button_single</Button>
<!--Click the button "Button_list", and the value of parameter obtained from ViewModel is null. Why?-->
<Button Command="{Binding BtnCommand1}" CommandParameter="{Binding ElementName=webView_list}">Button_list</Button>
<webview2:WebView2 Name="webView_single" Source="https://www.google.com/" Grid.Row="1">
<behaviour:Interaction.Triggers>
<behaviour:EventTrigger EventName="NavigationCompleted">
<behaviour:CallMethodAction TargetObject="{Binding}" MethodName="webView2_NavigationCompleted" />
</behaviour:EventTrigger>
</behaviour:Interaction.Triggers>
</webview2:WebView2>
</StackPanel>
<ScrollViewer Grid.Row="2" >
<ItemsControl Grid.Row="1" ItemsSource="{Binding AccountDtos}" HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<md:TransitioningContent OpeningEffect="{md:TransitionEffect Kind=ExpandIn}" >
<Grid Width="600" MinHeight="800" MaxHeight="250" Margin="8" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border CornerRadius="4" Grid.RowSpan="5" Background="#7F7F7F" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserName:" />
<TextBlock Padding="5,0" Text="{Binding UserName}" />
<TextBlock Text="Password:" />
<TextBlock Padding="5,0" Text="{Binding Password}" />
</StackPanel>
<webview2:WebView2 Name="webView_list" Source="https://www.google.com/" Grid.Row="1">
<behaviour:Interaction.Triggers>
<behaviour:EventTrigger EventName="NavigationCompleted">
<behaviour:CallMethodAction TargetObject="{Binding}" MethodName="webView2_NavigationCompleted" />
</behaviour:EventTrigger>
</behaviour:Interaction.Triggers>
</webview2:WebView2>
</Grid>
</md:TransitioningContent>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
I want to pass the webview2 control on the page to the ViewModel after clicking the button.
The following is part of the code for xaml:
public DelegateCommand<object> BtnCommand1 { get => new DelegateCommand<object>(Execute1); }
private void Execute1(object parameter)
{
//In the xaml page, if you click the button "Button_single", you can get the parameter value of Microsoft.Web.WebView2.Wpf.WebView2 type
//If you click the button "Button_list", the value of parameter is null. Why and how can it not be null?
Console.Write(parameter);
}
I want to get the webView control in the list control in the ViewModel. How should I write this code?
(This question Passing a Different button in Command Parameter Wpf Similar to my question, but different)
There are two reasons the webView_list commandparameter cannot work.
The first is conceptual. There are going to be a list of those WebView2s rather than just one. How's it supposed to know which one it is to reference?
You know if you have a list of anything in c# then you'd have to reference by index or something which one in the list you want.
The same would be true for xaml. You'd need to tell it to go look at row 0 or 1 or 2 or whatever.
There is another complication though. WPF has the concept of namescopes. All the controls directly in the grid of your window are in the same namescope.
When you have templates and in particular lists of things that becomes a bit more complicated.
Let's think about this from the bottom up though.
You've named a control webView_list. This is in an itemtemplate. So when you have 4 items you will have 4 WebView2s. But it will not error. Somehow those 4 named controls do not collide with one another.
This is because each of those items produced from that itemtemplate have their own namescope. The first item has a namescope of it's own, the second has it's own namescope etc.
You cannot just use elementname to reference something in a different namescope. So you can't get at a specific webview2 in that list using elementname from outside the item it's in.
In any case, you need some way to tell which item.
You could use selecteditem if this was a listbox.
An itemscontrol has no selection though. To get at your webview2 in an item the usual thing to do would be to put the button in the itemtemplate and the same namescope. That could then use relativesource to get to the command in the parent datacontext. Something like:
<ItemsControl.ItemTemplate>
<DataTemplate>
<md:TransitioningContent OpeningEffect="{md:TransitionEffect Kind=ExpandIn}" >
<Grid Width="600" MinHeight="800" MaxHeight="250" Margin="8" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border CornerRadius="4" Grid.RowSpan="5" Background="#7F7F7F" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserName:" />
<TextBlock Padding="5,0" Text="{Binding UserName}" />
<TextBlock Text="Password:" />
<TextBlock Padding="5,0" Text="{Binding Password}" />
</StackPanel>
<Button Command="{Binding DataContext.BtnCommand1, RelativeSource={RelativeSource AncestorType ItemsControl}}"
CommandParameter="{Binding ElementName=webView_list}" Grid.Row="2">Button_list</Button>
<webview2:WebView2 Name="webView_list" Source="https://www.google.com/" Grid.Row="2">
<behaviour:Interaction.Triggers>
<behaviour:EventTrigger EventName="NavigationCompleted">
<behaviour:CallMethodAction TargetObject="{Binding}" MethodName="webView2_NavigationCompleted" />
</behaviour:EventTrigger>
</behaviour:Interaction.Triggers>
</webview2:WebView2>
</Grid>
</md:TransitioningContent>
</DataTemplate>
</ItemsControl.ItemTemplate>

UserControl in DataTemplate does not bind correctly

I've been trying to create a custom menu. For this reason I wanted to use an ItemsControl in order to make it flexible. After hours of headache I figured out how to make it - kinda.
I have my custom ItemsControl "LiftMenu" (which is not yet much custom but standard) and an UserControl called "LiftItem". Last but not least I got the Model-class "LiftMenuItem".
By adding a new LiftMenuItem to the LiftMenu, it should display a new LiftItem-control as corresponding item. So far so good, I managed to get this working.
In that LiftItem-control I bind like I would in a normal DataTemplate: plain bindings with a path, nothing more. Normally this would work just fine because the DataTemplate already has it's context set to the model-type.
But now I just get an empty control that does nothing and shows nothing, because the bindings don't work.
I implemented it this way:
<menu:LiftMenu HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="200" Background="#80A8A8A8" Margin="5,0,0,0">
<menu:LiftMenu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</menu:LiftMenu.ItemsPanel>
<menu:LiftMenu.ItemTemplate>
<DataTemplate DataType="{x:Type menu:LiftMenuItem}">
<menu:LiftItem />
</DataTemplate>
</menu:LiftMenu.ItemTemplate>
<menu:LiftMenuItem Header="Test1"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border x:Name="border" BorderBrush="{Binding LabelColor}" BorderThickness="1" HorizontalAlignment="Left" Height="Auto" Margin="0"
VerticalAlignment="Stretch" Width="4" Background="{Binding BorderBrush, ElementName=border}"/>
<TextBlock Text="{Binding Header}" TextTrimming="CharacterEllipsis" Margin="5,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center"
Foreground="White" />
<controls:ProgressRing x:Name="ring" Grid.Column="2" HorizontalAlignment="Right" Stroke="#ffff8000" VerticalAlignment="Center"
Minimum="0" Maximum="100" Value="{Binding ProcessValue}" IsIndeterminate="{Binding ProcessIndeterminate}" Visibility="{Binding ProcessVisibility}"
Width="20" Height="20" Radius="10" Margin="2,0,2,0" />
</Grid>
In the end there is no text, no border. Just the ProgressRing is visible.
How can I fix this? This ListItem-control should become similiar to a button, thus I need to do some styling (animation, ...). I can't do this within a normal DataTemplate, but I don't want to miss the binding features of WPF on that. This would make it relatively unflexible.
What's the problem? I probably just miss some DataContext or so, but I don't know what it would be.

Design Xaml grid with height = 100% and minimum row height size

Here's the problem. I have a grid with some data written in xaml:
<ItemsControl x:Name="ItemsControl" ItemsSource="{Binding Path=MyObjectCollection, UpdateSourceTrigger = PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="27"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MaxHeight="75" MinHeight="30"></RowDefinition>
</Grid.RowDefinitions>
<Label Name="LabelNumber" Content="{Binding ObjectID}" Grid.Column="0" Style="{StaticResource MyStyleLabel}" />
<control:FocusMeterControl x:Name="HorizontalFocusMeterControl" Value="{Binding ObjectProperty}" Height="Auto" Grid.Column="1" />
<Button Name="RemoveObject" Content="-" Grid.Column="2" Margin="5,0,0,0" Click="ButtonBase_OnClick" Tag="{Binding Point}" Style="{StaticResource MyStyleButton}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As you can see I'm creating a new grid for each member in my "MyObjectCollection". Ideally I think I should just create one row instead since this would make my next problem - the real problem easier.
However, I have not found any good way to do this in xaml if even possible. Is this possible without populating the collection manually from c# and setting a row-property in my objects manually in order to do something like this
<Label Name="LabelNumber" Grid.Row="{Binding ManuallyCalculatedRowID}" ... />
My primary problem is that I would like the grid/rows to all be equally high and if possible fill out the parent window. If the parent window is way too large MaxHeight should apply and I would just like some empty space below.
The parent control is a Windows Form ElementHost if that makes any difference.
Please let me know if you need any additional info.
To make all rows of equal hight we can use ItemsPanelTemplate and set it to UniformGrid that will take care of equal sizing problem:
<ItemsControl x:Name="ItemsControl" ItemsSource="{Binding Path=MyObjectCollection, UpdateSourceTrigger = PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="27"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Label Name="LabelNumber" Content="{Binding ObjectID}" Grid.Column="0" Style="{StaticResource MyStyleLabel}" />
<control:FocusMeterControl x:Name="HorizontalFocusMeterControl" Value="{Binding ObjectProperty}" Height="Auto" Grid.Column="1" />
<Button Name="RemoveObject" Content="-" Grid.Column="2" Margin="5,0,0,0" Click="ButtonBase_OnClick" Tag="{Binding Point}" Style="{StaticResource MyStyleButton}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that I removed row definition in ItemTemplate to lift vertical size restrictions for items. Not sure if that answers your question but that can be good starting point on the road to solution.

Silverlight Binding to parent DataTemplate

I have Array of Users and array of Roles.
For each user i'am rendering list of roles.
I want to bind checkboxes to some user property.
Here is my code ( line 38 ):
https://gist.github.com/sadgb/30e2b75f2fff159bc26e#file-gistfile1-xml-L38
Why this line:
{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Grid}, Path=DataContext}
...binds to Role class, not to User class?
Binding should find grid at line 21, which has DataContext of type User isn't it?
There is another way: -
1. Give any name to your grid.
2. then do ur binding like this :
DataContext="{Binding ElementName=Gridname,Path=DataContext}"
Here is your edited code:
<DataTemplate DataType="usersService:User">
<Grid Name="TheGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding UserId}"/>
<telerik:RadListBox Grid.Row="1"
ItemsSource= "{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=users:UsersPage}, Path=RolesViewModel.Roles}" IsTextSearchEnabled="false">
<telerik:RadListBox.ItemTemplate>
<DataTemplate DataType="accessControlSubsystem:Role">
<StackPanel>
<CheckBox
IsChecked="{Binding ElementName=TheGrid,Path=DataContext , Converter={StaticResource CheckBoxToSelectedArrayConverter}, Mode=TwoWay}" Content="{Binding RoleName}" Tag="{Binding RoleId}" />
</StackPanel>
</DataTemplate>
</telerik:RadListBox.ItemTemplate>
</telerik:RadListBox>
</Grid>
</DataTemplate>
I guess this will solve your problem or at least give you a good start.
One more thing I noticed in your code is that you didn't specify the property name in your relative binding:
IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Grid}, Path=DataContext.PopertyName, Converter={StaticResource CheckBoxToSelectedArrayConverter}, Mode=TwoWay}" Content="{Binding RoleName}" Tag="{Binding RoleId}"
Can you move the level up one using "ancestorLevel"
{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Grid}, AncestorLevel=1, Path=DataContext}
Oliver

Binding different Target on loaded DataTemplateSelector

I am using a DataTemplateSelector to select different UserControls (reference http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector), according the selected path I select the needed UserControl.
The problem is now, when using the WebBrowser Control, I should bind it to ActualHight of MyScrollViewer, but on all others it works with Hight or else the scroll bar is displayed bad. Must come from the WebBrowser control.
How can I switch the Bindings in the ContentControl between Hight/ActualHight depending of the loaded UserControl?
<DataTemplate x:Key="WebTemplate1">
<DockPanel LastChildFill="True">
<controls:WebBrowserUserControl SourceHtml="{Binding Converter={StaticResource UriConverter1}}" />
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="ImgTemplate1">
<Image Source="{Binding Converter={StaticResource RelativeToAbsolutePathConverter1}}"
Stretch="None" />
</DataTemplate>
...
<ScrollViewer Name="MyScrollViewer"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
DockPanel.Dock="Left"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Grid x:Name="MyGridHelper">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DockPanel x:Name="MyDockPanel" Dock="Top" HorizontalAlignment="Left">
<ContentControl x:Name="MyContentControl"
Width="{Binding ElementName=MyScrollViewer,
Path=Width/ActualWidth}"
Height="{Binding ElementName=MyScrollViewer,
Path=Height/ActualHight}"
Content="{Binding Path=CurrentItem1,
Mode=OneWay}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}" />
</DockPanel>
</Grid>
</ScrollViewer>
The DataTemplateSelector by itself cannot affect the other properties of the ContentControl, however, you could use a Converter to determine the Width\Height based on the same logic used to determine which template to use. So something like this:
<ContentControl x:Name="MyContentControl"
Width="{Binding ElementName=MyScrollViewer, Converter={StaticResource MyWidthConverter}, ConverterParameter="???"}"
Height="{Binding ElementName=MyScrollViewer, Converter={StaticResource MyHeightConverter}, ConverterParameter="???"}"
Content="{Binding Path=CurrentItem1, Mode=OneWay}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}" />
I put question marks for the ConverterParameter because I'm not clear on how you determine which case calls for which Width/Height. But you can pass in a parameter that will allow you to decide which value to pass back, and based on that decision, the Converter can determine whether to get the ActualWidth/ActualHeight or the Width/Height of the ScrollViewer that is passed in.

Categories