So I have a view, containing a telerik RadGridView, this view is bound to several items, but importantly I need to bind the visibility of an item in one column, to 2 items.
The converter will correctly evaluate the visility, however I need to pass back the previousProc, (currently handled) and also "This" which is a proc as well, just that row.
<telerik:RadGridView Name="ProcedureGrid"
DockPanel.Dock="Left"
SelectionMode="Single"
SelectionUnit="FullRow"
ItemsSource="{Binding Procedures}"
IsReadOnly="True"
AutoGenerateColumns="False"
ShowGroupPanel="False"
ShowColumnHeaders="False"
CanUserReorderColumns ="False"
RowIndicatorVisibility="Collapsed"
Visibility="Collapsed"
Width="200"
FontSize="18"
SelectionChanged="ProcedureGrid_SelectionChanged"
>
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn Header="Name"
AllowDrop="False"
DataMemberBinding="{Binding Converter={StaticResource langConverter}}"
IsGroupable="False"
IsFilterable="False"
MaxWidth="155"/>
<telerik:GridViewColumn>
<telerik:GridViewColumn.CellTemplate>
<DataTemplate>
<nav:SmallForwardNavigateIcon MaxWidth="30" DockPanel.Dock="Right" Margin="1"
Cursor="Hand" VerticalAlignment="Center" HorizontalAlignment="Center"
MouseDown="SmallForwardNavigateIcon_MouseDown"
Visibility="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}},
Path=DataContext.previousProc,
Converter={StaticResource IsPrevProc}}" />
</DataTemplate>
</telerik:GridViewColumn.CellTemplate>
</telerik:GridViewColumn>
</telerik:RadGridView.Columns>
</telerik:RadGridView>
can anyone see where I went wrong and what I could do to fix the xaml to pass both the previousproc and This back
If I understand your UserControl host a telerik:RadGridView control.
Your UserControl has a given DataContext, which seems to conatin a property Procedures, and a property IsPrevProc.
Visibility="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=DataContext.previousProc,Converter={StaticResource IsPrevProc}}" />
This code seems wrong because you write:
Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}
It means you are looking for properties in your ancestor DataContext, the one containing Procedures and IsPrevProc. So all bindings here have to be with properties of this DataContext. You can't mix in one binding call to different DataContext.
What you could do is create your "previousProc" as a property in this DataContext, so that you can call it directly.
Or you can define "IsPrevProc" as a property of the DataContext of a line of your Grid.
But you can't do both in the same binding.
The ConverterParameter property is not a dependency property and hence can not be bound.
There is however an alternative solution. You could use a MultiBinding with a multi-value converter instead of a normal Binding:
<nav:SmallForwardNavigateIcon MaxWidth="30" DockPanel.Dock="Right" Margin="1"
Cursor="Hand" VerticalAlignment="Center" HorizontalAlignment="Center"
MouseDown="SmallForwardNavigateIcon_MouseDown"
>
<nav:SmallForwardNavigateIcon.Visibility>
<MultiBinding Converter="{StaticResource IsPrevProc}">
<Binding Path="DataContext.previousProc" RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=UserControl}"/>
<Binding Path="DataContext.newProc" RelativeSource="{RelativeSource Mode=Self}"/>
</MultiBinding>
</nav:SmallForwardNavigateIcon.Visibility>
</nav:SmallForwardNavigateIcon>
Pass the new proc/this value in second binding.(use relative source if needed)
MultiValue Converter:
public class IsPrevProc : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//Logic of new proc and Previous Proc
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Related
I have to create a menu. It has 10 entries and they differ by one parameter.
Entry 1:
<MenuItem Visibility="{Binding MenuSelected.Type, Converter={StaticResource TypeToVisibilityConverter}, ConverterParameter='PAZ', Mode=OneWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding CmdContextMenu}" CommandParameter="PAZ" />
</i:EventTrigger>
</i:Interaction.Triggers>
<MenuItem.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="Segoe MDL2 Assets"
Foreground="{Binding MenuSelected.Type, Converter={StaticResource TypeToColorConverter}, ConverterParameter='PAZ', Mode=OneWay}"
Text="{Binding MenuSelected.Type, Converter={StaticResource TypeToIconConverter}, ConverterParameter='PAZ', Mode=OneWay}" />
<TextBlock
Grid.Column="1"
Margin="{StaticResource XSmallLeftMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="PAZ" />
</Grid>
</MenuItem.Header>
</MenuItem>
Entry 2:
<MenuItem Visibility="{Binding MenuSelected.Type, Converter={StaticResource TypeToVisibilityConverter}, ConverterParameter='APP', Mode=OneWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding CmdContextMenu}" CommandParameter="APP" />
</i:EventTrigger>
</i:Interaction.Triggers>
<MenuItem.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="Segoe MDL2 Assets"
Foreground="{Binding MenuSelected.Type, Converter={StaticResource TypeToColorConverter}, ConverterParameter='APP', Mode=OneWay}"
Text="{Binding MenuSelected.Type, Converter={StaticResource TypeToIconConverter}, ConverterParameter='APP', Mode=OneWay}" />
<TextBlock
Grid.Column="1"
Margin="{StaticResource XSmallLeftMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="APP" />
</Grid>
</MenuItem.Header>
</MenuItem>
As you can see, the only difference is between PAZ and APP in different points... and same goes on for all the others.
Is there a way I can avoid to repeat it 10 times just changing the 3 letters?
I do not want to use code-behind to create the menu dynamically... but to process it from XAML.
From your question I assume that CmdContextMenu and MenuSelected are properties on your main view model and not in a separate menu item type. If this is different, you have to adapt the code accordingly.
In order to remove the redundant code, create a collection for your menu items in your view model.
public class MyMainViewModel : INotifyPropertyChanged
{
public IEnumerable<string> MyTypes { get; } = new List<string>
{
"PAZ",
"APP"
};
// ...other properties and methods (like "CmdContextMenu" and "MenuSelected" I Assume).
}
Next, you have to change the value converters, because the ConverterParameter is not a dependency property and cannot be bound. Instead, you can use IMultiValueConverters that can bind multiple values. Adapt all of your converters to implement IMultiValueConverter. Here is an example of how a converter could look like. Of course, it depends on your implementation. In essence, use the values array to access bound values.
public class TypeToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].Equals(values[1]) ? Visibility.Visible : Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Next, create a data template for the header of your menu items. As you can see, the bindings are replaced with MultiBindings that use an IMultiValueConverter as Converter.
The first binding in each block is a RelativeSource binding to access the data context of the parent Menu, because I assume that the MenuSelected property is defined on your main view model. The other empty bindings will bind to the data context of the current menu item, which is an item from the MyTypes collection.
<DataTemplate x:Key="MyMenuItemHeaderTemplate" DataType="{x:Type system:String}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="Segoe MDL2 Assets">
<TextBlock.Foreground>
<MultiBinding Converter="{StaticResource TypeToColorConverter}">
<Binding Path="DataContext.MenuSelected.Type" RelativeSource="{RelativeSource AncestorType={x:Type Menu}}" Mode="OneWay"/>
<Binding/>
</MultiBinding>
</TextBlock.Foreground>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TypeToIconConverter}">
<Binding Path="DataContext.MenuSelected.Type" RelativeSource="{RelativeSource AncestorType={x:Type Menu}}" Mode="OneWay"/>
<Binding/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Grid.Column="1"
Margin="{StaticResource XSmallLeftMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding}"/>
</Grid>
</DataTemplate>
Create a new header item style that uses this data template. The Visibility also uses a multi-value converter as above. Instead of using an event trigger, you can simply assign the command to the menu item directly and pass a CommandParameter, which is bound to the data context of the current menu item.
<Style x:Key="MyMenuItemStyle" TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="HeaderTemplate" Value="{StaticResource MyMenuItemHeaderTemplate}"/>
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource TypeToVisibilityConverter}">
<Binding Path="DataContext.MenuSelected.Type" RelativeSource="{RelativeSource AncestorType={x:Type Menu}}" Mode="OneWay"/>
<Binding/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Command" Value="{Binding DataContext.CmdContextMenu, RelativeSource={RelativeSource AncestorType={x:Type Menu}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
Finally, create a Menu and bind the MyTypes collection, as well as the style.
<Menu ItemsSource="{Binding MyTypes}" ItemContainerStyle="{StaticResource MyMenuItemStyle}"/>
ConverParameter property is not a DependencyProperty - so it cannot be Bound to.
You can use a MultiValue converter instead.
Instead of creating 10 menu items in xaml manually, you should be able to bind an ItemsCollection and define a DataTemplate for MenuItem
Consider a WPF DataGrid with DataGridTemplateColumn(s) and with user initiated adding CanUserAddRows="True" allowed, for example
<DataGrid AutoGenerateColumns="False" CanUserAddRows="True" ItemsSource="{Binding Options}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton IsChecked="{Binding IsChecked}" GroupName="OptionsRad"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Option" Binding="{Binding OptionName}"/>
</DataGrid.Columns>
</DataGrid>
The RadioButton thanks to being hosted DataGridTemplateColumn can be interacted with without entering CellEditingMode, which is expected and desired.
There is, however, the problem that on the last, 'add new option', type-of-row the RadioButton can be interacted with even before new option is added (i.e. will be added name is entered). Possibly choosing non-existent option and moving focus elsewhere.
How can I disable interacting with the template column before new row is added to bound collection?
You can compare with NewItemPlaceholder purely in XAML as well:
<DataTemplate>
<RadioButton x:Name="Radio" IsChecked="{Binding IsChecked}" GroupName="OptionsRad" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Item, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridRow}}"
Value="{x:Static CollectionView.NewItemPlaceholder}">
<Setter TargetName="Radio" Property="IsEnabled" Value="False" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You could use a converter to achieve what you are trying to do.
I assumed you have a class or data type to represent your Option data and Options is a collection of those items.
You could set IsEnabled on RadioButton to:
IsEnabled="{Binding Path=Item, RelativeSource={RelativeSource AncestorType=DataGridRow"}, Converter={StaticResource DataToEnabledConverter}}"
And, the converter code would look like:
public class DataToEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is Option;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Your full XAML would look like:
<DataGrid AutoGenerateColumns="False" CanUserAddRows="True" ItemsSource="{Binding Options}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton IsChecked="{Binding IsChecked}" GroupName="OptionsRad"
IsEnabled="{Binding Path=Item, RelativeSource={RelativeSource AncestorType=DataGridRow"}, Converter={StaticResource DataToEnabledConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Option" Binding="{Binding OptionName}"/>
</DataGrid.Columns>
</DataGrid>
This works because Item property on DataGridRow for new rows would be of type NamedObject, not your data type.
I'm using .Net 4.0 and have a DataGrid in a view. I have implemeneted this http://www.codeproject.com/Articles/42227/Automatic-WPF-Toolkit-DataGrid-Filtering to provide filtering. The ItemsSource for the DataGrid is an observable collection of my custom objects.
Each row has a Button that when clicked, passes the selected custom object back via the CommandParameter.
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Path=DataContext.DeleteMyCustomObjectCommand,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding}" Width="20">
<Image Source="/MyThing;component/Images/delete.png" Height="16" Width="16"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
I want to be able to save the filter criteria using this solution http://www.codeproject.com/Articles/42227/Automatic-WPF-Toolkit-DataGrid-Filtering?msg=3342202#xx3342202xx but one of the calls requires a reference to the data grid
QueryController queryController= DataGridExtensions.GetDataGridFilterQueryController(myGrid1);
As i'm using MVVM, I don't have a reference to the DataGrid in my ViewModel. My command execution code (in the ViewModel) looks like this at present
public void DeleteMyCustomObject(object param)
{
MyCustomObject m = param as MyCustomObject;
.....Deletion commands go here
Is there a way I can use multibindings on the CommandParameter of my Delete button to pass back the custom object from the current row, and a reference to the actual DataGrid (or is there a better solution).
Many Thanks
Mick
(1) bound the DataGrid.SelectedItem to a Property in you ViewModel .
(2) send the Grid as the CommandParameter .
<DataGrid Grid.Column="2" Name="DG1" ItemsSource="{Binding}" SelectedItem="{Binding SelectedItem}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Path=DataContext.DeleteMyCustomObjectCommand,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}" Width="20">
<Image Source="/MyThing;component/Images/delete.png" Height="16" Width="16"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn>
<DataGrid.Columns>
</DataGrid>
I Have a WPF treeview and i need the reference of parent node in the child node context.
menu command. In the below XAML, i need to pass the reference of A in member command parameter
XAML:
<DataTemplate x:Key="Member">
<TextBlock Text="{Binding}" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=mylib:ExtendedTreeView}}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.Tag.DeleteMmeberCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource MutilValueConverter}">
<Binding Path=".."/>
<Binding />
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type A}" ItemsSource="{Binding Members}" ItemTemplate="{StaticResource Member}"
<TextBlock Text="{Binding"}>
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.Tag.DeleteACommand}" CommandParameter="{Binding}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
<TreeView ItemsSource="{Binding As}"/>
Converter:
public class MutilValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
If i understand correctly, you could possibly invert the whole thing:
publish your command in what is your datacontext and give an instance
of your subdatacontext as command parameter (this is just Binding for
your items)
You are using PlacementTaregt in your bindings but you havent set the ContextMenu.PlacementTarget anywhere...
<TextBlock Text="{Binding"} x:Name="MyTextBox">
<TextBlock.ContextMenu>
<ContextMenu PlacementTarget="{Binding ElementName=MyTextBox}">
.....
the straight way is to have viewmodels for what your Members collection is holding.
and with child viewmodels, there is no need for getting in the binding, as you can just hold the data needed in the viewmodel class. it is an adapter between your model (whereever the strings come from) and your view (where the strings are displayed).
I have a combobox which I want to call a method from MainViewModel but it binds to EmployeesOverviewViewModel. Is it possible to do this? if yes - how?
Here is my code for the combobox
<ComboBox ScrollViewer.CanContentScroll="False" Text="Select Employees" DataContext="{Binding EmployeesOverviewViewModel, Source={StaticResource ViewModelLocator}}" Name="employeeComboBox" ItemsSource="{Binding Employees}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected}" Content="{Binding Path=Name}" Width="{Binding ElementName=employeeComboBox, Path=ActualWidth}" VerticalAlignment="Center">
</CheckBox>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I have thought about using Command but I couldn't figure out the binding problem.
BR
if your MainViewModel is anywhere is the visual tree as the DataContext, then you can achieve what you want with RelativeSource in your CommandBinding for your ComboBox.
In expanding on my comment to the original post. Shown below is an example of how to bind to the data context of the parent.
Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.SomeCommand}"
Set the path to command on the viewmodel you want to bind to.
Do both ViewModels know each other? Then the EmployeesOverviewViewModel could provide an delegate that you would execute and the MainWindowViewModel could use this delegate to "bind" it to its method. (edit: it would be enough if the MainWindowViewModel knows the EmployeesOverviewViewModel)
Otherwise you could try to use a binding using FindAncestor and try to get the MainWindowView. The problem then would be that you don't just need the view itself but its DataContext.
I would say you have two realistic approaches.
1) if your view can see a suitable instance of MainViewModel, you should be able to bind to a command or whatever you need on that by using StaticResource or DynamicResource. I see you're using a StaticResource to find the viewmodel providing the ComboBox's items, would something similar work for finding MainViewModel?
2) if your view can't see a MainViewModel, but your viewmodel can, get the viewmodel to expose a suitable command or method and just have that call MainViewModel's version. This is cleaner, as then your EmployeesOverviewView doesn't have to know anything about MainViewModel at all.
I'd prefer option 2.
You can do that using a MultiValueConverter. I show you a small sample.
I have a window with 3 CheckBox:
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="300" Width="400" mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:WpfApplication7="clr-namespace:WpfApplication7" d:DesignHeight="371"
d:DesignWidth="578" SizeToContent="WidthAndHeight">
<Window.Resources>
<WpfApplication7:MultiBooleanConverter x:Key="multiBooleanConverter" />
</Window.Resources> <Grid>
<CheckBox Content="Hallo">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource ResourceKey=multiBooleanConverter}">
<Binding ElementName="checkBox1" Path="IsChecked"/>
<Binding ElementName="checkBox2" Path="IsChecked"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
<CheckBox x:Name="checkBox1" Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="64,72,0,0" VerticalAlignment="Top" />
<CheckBox Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="62,120,0,0" x:Name="checkBox2" VerticalAlignment="Top" />
</Grid>
</Window>
The MultiValueConverter is declared like that:
public class MultiBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Cast<bool>().Any(b => b);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
object[] returnValue = new object[targetTypes.Length];
for (int i = 0; i < targetTypes.Length; i++)
{
returnValue[i] = (bool)value;
}
return returnValue;
}
}