How to bind to 2 different members in a class in WPF? - c#

I have a class like:
class EditorViewModel
{
public ObservableCollection<Effect> AllEffects;
public bool HasPermissions;
}
But the problem is, when I am trying to bind AllEffects to ListView, then I can't bind anything to HasPermissions because the binding scope is limited to AllEffects, not EditorViewModel.
I tried this but it doesn't work:
<ListView ItemsSource="{Binding EditorViewModel}">
...
<GridViewColumn Width="Auto" Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding AllEffects.Name}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="Auto" Header="Type">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding AllEffects.Type}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
If I set the ItemsSource to EditorViewModel and get rid of AllEffects, it works. But then I don't know how to access HasPermissions through binding:
<GridViewColumn Width="50" Header="Override">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="0"
HorizontalAlignment="Center"
IsEnabled="{Binding HasPermission}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>

As I updated my answer on this question to include, you can bind the ListView to the AllEffects property of your ViewModel and then refer to a different property of the ViewModel using a relative binding. So assuming your ListView is contained in a Window whose DataContext is an EditorViewModel, and the ListView's ItemsSource is AllEvents, you can still reference HasPermission like so:
<CheckBox Margin="0"
HorizontalAlignment="Center"
IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.HasPermission}"/>
That somewhat clunky notation will find the nearest parent element to the CheckBox in the visual tree that is of type Window, and bind to its DataContext property to find HasPermission.

A classic trick is to use ViewModelLocator, see:
MVVM Light - using ViewModelLocator - properties hit multiple times
also, for a more quick-and-dirty solution, you can use the following Binding:
{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}, Path=DataContext.HasPermissions}
Note that this would only work on WPF and not in SL, since SL doesn't support this syntax of RelativeSource.

Related

C# WPF : binding a listview left button click to a data item command

Considering this xaml :
<ListView ItemsSource="{Binding Items}" PreviewMouseLeftButtonDown="{Binding CheckItemCommand}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.Header>
<CheckBox IsThreeState="True"
IsChecked="{Binding IsSelectAll, Mode=TwoWay}"
Command="{Binding CheckAllCommand}">
</CheckBox>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate DataType="x:MyObject">
<Grid>
<TextBlock Text="{Binding LoadingState}"/>
<CheckBox IsThreeState="False"
IsChecked="{Binding IsSelected, Mode=TwoWay}"
Command="{Binding CheckItemCommand}">
</CheckBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{Binding MyObject.Header_Name}"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="{Binding MyObject.Header_CreationDate}"
DisplayMemberBinding="{Binding CreationDate}" />
<GridViewColumn Header="{Binding MyObject.Header_NumberOfStuff1}"
DisplayMemberBinding="{Binding stuff1}"/>
<GridViewColumn Header="{Binding MyObject.Header_NumberOfStuff2}"
DisplayMemberBinding="{Binding stuff2}"/>
</GridView>
</ListView.View>
</ListView>
I want to call the CheckItemCommand, which is already bound on the row checkbox, when i click anywhere on the row.
How can i specify the clicked row with
PreviewMouseLeftButtonDown="{Binding CheckItemCommand}"
?
I found something that works. I'm not very found of this solution but it works fine, it's very simple to understand and can be done with very fiew code ( => KISS principle).
To have a proper "OnRowClick => DoStuff()" behavior I intercept all left click then, if the source is of the proper type, i call the Command_Execute() method.
Code :
public MyContainer()
{
InitializeComponent();
this.PreviewMouseDown += MyContainer_PreviewMouseDown;
}
private void MyContainer_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var source = e.OriginalSource as FrameworkElement;
var dataContext = source?.DataContext;
if(dataContext.GetType() == typeof(ProperViewModel))
{
((ProperViewModel)dataContext).Command_Execute();
}
}
I'm pretty sad there is no native way to do it via a mere Command pattern...
If someone has a better way to do it, feel free to had another, more elegant answer
You could use an interaction trigger:
<ListView ItemsSource="{Binding Items}"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown" >
<i:InvokeCommandAction Command="{Binding CheckItemCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
...
</ListView>
The EventTrigger is defined in System.Windows.Interactivity.dll which is part of the Blend SDK: Visual Studio 2017 - What happened to Expression interactions?
Please refer to the following blog post for more information: https://blog.magnusmontin.net/2013/06/30/handling-events-in-an-mvvm-wpf-application/.
Instead of events I would use a mousebinding.
Which you apply using a style:
When you click a row you're going to make it the selected item so you might be able to use that. Or maybe not, since I see you have some selecting going on there already.
<Style TargetType="ListViewItem">
<Setter Property="local:AddToInputBinding.Binding">
<Setter.Value>
<MouseBinding Gesture="LeftClick" Command="{Binding YourCommand}" />
</Setter.Value>
</Setter>
The code you use for your command could use a property you bind to selecteditem.
If that doesn't suit then you can use a commandparameter to provide the item clicked.
I think that would look like:
CommandParameter="{Binding}"
That will give you the entire object that row has as it's datacontext - the item from Items.
If your command is not in whatever object is presented to each item in the listview then there is a bit of a complication.
You need to go find the datacontext of the listview ( as opposed to the row your mousebinding is going to end up "in" ).
Do that using relativesource.
That aspect will look something like:
Command="{Binding DataContext.YourCommand,
RelativeSource={RelativeSource
AncestorType={x:Type ListView}}}"
Rather than just do this for you, I've tried to explain the principles involved.
If you're new to commands and parameters then mvvmlight is very good and this article about relaycommand might clarify:
https://msdn.microsoft.com/en-us/magazine/dn237302.aspx

How to access checkboxes in a List view one by one

I want to check all text boxes on the basis of if the check box is checked. But I don't know how to access all check boxes one by one? I bound it with ApprovalStatus which is of boolean type. Can any one help me to have code in C#?
<CheckBox Content="Check All" Height="16" HorizontalAlignment="Left" Margin="9,193,0,0" Name="Tab2CheckAll" VerticalAlignment="Top" Width="77" Click="Tab2CheckAll_Click"/>
<ListView Height="213" HorizontalAlignment="Left" Margin="9,215,0,0" Name="Tab2EmployeeEffortList" VerticalAlignment="Top" Width="771" AllowDrop="True" IsTextSearchEnabled="True">
<ListView.View>
<GridView>
<GridViewColumn Header="Approved" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox CommandParameter="{Binding}" IsChecked="{Binding ApprovalStatus}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You need not to access individual checkBoxes. CheckBox is already bounded to property set that instead.
Loop over ItemsSource of ListView and set ApprovalStatus to true for all items in the collection. As long as your underlying source class is implementing INotifyPropertyChanged, it will work fine.

Image doesn't bind to the resource. How to use Relativesource for Binding

I am using Binding to fill the contents of a ListView and also an Image. However for some reason the Image doesn't get it's value through binding.
If I go like
<Image Source="/Images/LedGreen.png"/>
The image shows up where it should be, however using binding with just substituting the Relative address with a binding argument and handing him the value using a PackUri in the constructor doesn't.
The code is:
<Window.Resources>
<CollectionViewSource
x:Key="DeviceList"
Source="{Binding Path=DiscoveredDevicesList}">
</CollectionViewSource>
</Window.Resources>
.
.
.
<ListView
Grid.Row="1"
Width="500"
HorizontalAlignment="Left"
Margin="10"
Grid.Column="1"
DataContext="{StaticResource DeviceList}"
ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="Device name" DisplayMemberBinding="{Binding Path=DeviceName}"/>
<GridViewColumn Header="Rssi" DisplayMemberBinding="{Binding Path=Rssi}"/>
<GridViewColumn>
<GridViewColumnHeader Content="GPS" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<Image Source="{Binding Path=ImagePath}"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
and the relative property and instructors are:
public Uri ImagePath { get; set; }
public MainWindowViewModel()
{
ImagePath = new Uri("pack://application:,,,/Images/LedRed.png");
}
I am guessing since I am using Window.Resources I am facing this problem. However I want to make sure it's not a silly mistake before I scrub it off and do it the other way.
Thanks.
Property ImagePath seems to be located in window's DataContext i.e. MainWindowViewModel. So, you need to traverse to window's DataContext for binding to work.
Use RelativeSource to get the window's DataContext:
<Image Source="{Binding Path=DataContext.ImagePath,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
Here Image data context is DeviceList, because you are setting DeviceList as date context of list view. But ImagePath
Is in MainWindowViewModel. Since MainWindowViewModel is data context in Window you need to refer that data context.
<Image Source="{Binding ImagePath, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>

Binding combobox in DataTemplate to a different ItemSource

I have 2 ObservableCollection lists, which we can call A and B, then I have a GridView that I want to bind to list A and a ComboBox INSIDE that GridView, that I want to bind to list B.
I've set the ItemsSource property of the GridView by code: gridview.ItemsSource=A (and it works!). About the ComboBox its instance it is not available by code, I suppose because its definition it is enclosed between the DataTemplate tags; so I wonder how to bind the combo to list B, either by code or by XAML.
Follows the XAML code:
<ListView Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="lstReplacements" VerticalAlignment="Stretch">
<ListView.View>
<GridView>
<GridViewColumn HeaderContainerStyle="{StaticResource MyHeaderStyle}" Header="Wrong text" DisplayMemberBinding="{Binding Word}"/>
<GridViewColumn HeaderContainerStyle="{StaticResource MyHeaderStyle}" Header="Replacement" Width="60" DisplayMemberBinding="{Binding Replacement}" />
<GridViewColumn HeaderContainerStyle="{StaticResource MyHeaderStyle}" Header="Type" Width="30">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{??????}" DisplayMemberPath="??????" Grid.Row="1" Grid.Column="0" Name="cmbCorrectionType" Width="75" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Thanks in advance for the support!
Chris
I assume this control is in UserControl and you have set DataContext of that UserControl to the class instance where your both collections CollectionA and CollectionB resides.
You can then bind using RelativeSource:
<ComboBox ItemsSource="{Binding DataContext.CollectionB,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl}}"/>
Also you can set DataContext of ListView to the class instance and all you need to do is change AncestorType to ListView in place of UserControl in above binding.

Having trouble binding list/grid to collection

I have set up a grid and bound it to a collection. I can edit the items in the collection through my grid and the changes get propagated to the collection. And, the GUI is showing everything in the collection at the time the ItemSource is set. But, I am programmatically changing some of the items in the collection (after the ItemSource is set) and these changes aren't reflected in the grid/GUI. Is there something else I need to do in order to get it to refresh. FYI, for the fields I want to edit (MoveToResource, ResourceKey, and Resource Type), I have set the mode to TwoWay. Below is my grid.
<ListView Name="lstXAMLStrings" Margin="5" Grid.Row="1">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Extract?">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Content="" IsChecked="{Binding Path=MoveToResource, Mode=TwoWay}" ></CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Text">
<GridViewColumn.CellTemplate>
<DataTemplate>
<local:RichTextBlock RichText="{Binding Path=FormattedMatchedLines}" TextWrapping="Wrap" Width="650"></local:RichTextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Key Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=ResourceKey, Mode=TwoWay}" Width="150"></TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Resource Type">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource odp}}" SelectedItem="{Binding Path=Resource, Mode=TwoWay}"></ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
Does your [view]model class implement INotifyPropertyChanged and fire the event whenever the property set accessor is used?
You need to make sure that the collection itself that you're databinding to is an observable collection (a class that implements the INotifyCollectionChanged interface). You might be able to alternatively roll your own class that implements INotifyCollectionChanged, but that's the only reason ObservableCollection exists so it could save you some time.
There's an msdn article on how to do it.
You need to make sure that your collection items implement INotifyPropertyChanged.
If each item you're changing programatically (correctly) implements that, your ListView/GridView will stay current.
This will work if you modify your collection items programatically, or in another screen.

Categories