Passing View as Command Parameter in WPF.Does it violet MVVM approach? - c#

I have data grid and there some number of columns in it.There are number of rows. I want to show one window when user clicks on the context menu of that row.I need first columns value in viewmodel from that row for some logic.Currently I am passing placement target as command parameter i.e.gridviewrow. Following is my code
<telerik:RadGridView.RowStyle>
<Style TargetType="telerik:GridViewRow">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" ></Setter>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="show Window" Command="{Binding PlacementTarget.Tag.DataContext.ShowChart,RelativeSource={RelativeSource AncestorType=ContextMenu, Mode=FindAncestor}}" CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</telerik:RadGridView.RowStyle>
How can I pass value of first column of particular row on which user has clicked?
Does it violet MVVM approach?What is solution if it violets MVVM approach in this case?

Teoretically yes, because the only way to access the view is by INotifyPropertyChanged and IErrorDataInfo. However, it depends what do you inttend to do. If you want to change the visibilty of a UI elemnt, I violet the MVVM pattern because the other way arround it seems to complicated to me. I suggest to tell what exactly do you want to do, and maybe I will be able to help you :)

Maybe You can try to create a new ViewModel class for the grid's rows and each column is a property of the ViewModel. So you can easily bind the data context of the row to that ViewModel and the columns to its properties.

I will post another answer since you edited masively the question.
If you use a datagrid in your view, the your view (XAML code) should look like this:
<Window x:Class="CareerTrackWpfClient.Views.User_Main_Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="User_Main_Window" Background="Black">
<Window>
<Grid HorizontalAlignment="Center" Visibility="Collapsed" Name="gridbooks">
<DataGrid Height="650" HorizontalAlignment="Stretch" Name="BooksGrid" RowHeight="90" ColumnWidth="200"
ColumnHeaderHeight="40" HeadersVisibility="Column" Background="Transparent" RowBackground="DarkGray"
AlternatingRowBackground="LightBlue" BorderBrush="Gray"
BorderThickness="2" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Book #" Width="220" Binding="{Binding BookID}" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Book Title" Width="220" Binding="{Binding Title}" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
You should have a model like this
public class Book
{
public int BookID {get; set;}
public string Title {get;set;}
}
Now, either you do a ViewModel where you fill the list (like Zarzan said) - and this way you will respect the MVVM pattern. Or in the code beheind, in the constructor, bellow the InitializeComponent() method, you write something like this
List<Book> booksProvider=new List<Book>();
booksProvider.Add(new Book{BookID=1,"Book 1"}) ;
booksProvider.Add(new Book{BookID=2,"Book 2"}) ;
gridbooks.ItemsSource=booksProvider;
Is very nice using this UI component (DataGrid), from my point of view. It has the validations on place. Try to enter strings on the BookID field, and it will notify you. It is very flexible and you don't have to install anything.
Hope it helps as many developers as possible.

Related

WPF: Show Tooltip message on custom DataGrid cells when IDataErrorInfo

I'm pretty new to WPF but I've read a lot on it as well as MVVM in the last couple if days.
My WPF displays a DataGrid with custom column templates (using the NumericUpDown Controls from Xceed WPF Toolkit). Three of the columns contain the decimal coordinates of a 3D vector. I use IDataErrorInfo to make sure the vector's length is never 0 (all three columns cannot be 0 at the same time). This is working fine so far, the cells are marked red when the validation fails, but I also want to show the error message in a tooltip or similar.
<DataGrid [...]>
<DataGrid.Columns>
[...]
<DataGridTemplateColumn Header="X" [...]>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<xctk:DecimalUpDown Value="{Binding PositionX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
</xctk:DecimalUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
[... and same thing for Y and Z]
</DataGrid.Columns>
</DataGrid>
This is where I'm stuck for a few hours now, so I hope you can help me here:
How can I show a error tooltip on custom template columns?
I've read through a lot of articles and threads on error tooltips but most of them are on plain TextBox or DataGridTextColumns and a tried a lot but could not make it working so far.
Most of them look something like this:
<Style x:Key="errorStyle" TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="-2"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
from here:
https://msdn.microsoft.com/library/ee622975%28v=vs.100%29.aspx
or more exsamples:
Display validation error in DataGridCell tooltip
https://stackoverflow.com/a/7900510/5025424
https://harishasanblog.blogspot.de/2011/01/datagrid-validation-using.html
WPF data validation and display error message IDataErrorInfo and error templates
Nothing on this ever showed any Tooltip to me.
Can you give me a hint,
how this style trigger definition has to look for cells containing no TextBox,
where the definition has to be
and if the column needs the reference this definition in some way?
Thank you!
Set the Style property of the control to a Style with a trigger that sets the Tooltip property of the control in the CellTemplate if the attached Validation.HasError property returns true:
<DataGridTemplateColumn Header="X">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<xctk:DecimalUpDown Value="{Binding PositionX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
<xctk:DecimalUpDown.Style>
<Style TargetType="xctk:DecimalUpDown">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}" />
</Trigger>
</Style.Triggers>
</Style>
</xctk:DecimalUpDown.Style>
</xctk:DecimalUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

WPF DataGrid Custom Row Header

I am working on a DB FrontEnd with WPF / EntityFramework / MVVM
Now i got stuck when i allow the user to add data to a datagrid (which is bound to an Observable Collection).
What i want to achieve, is to get a row header like in MS Access:
So my WPF DataGrid should look like this basically:
Is there any way to bind the RowHeaderStyle to the RowState?
For Example:
RowState.Editing: Show Edit Icon
RowState.NewRow: Show Star
RowState.Default: Show Default Row Header
I found no solution so far, but i think WPF should pe powerfull enough to get this job done.
Thank you!
Simple. Give the DataGrid a RowHeaderStyle that swaps in different ContentTemplates depending on the state of the DataGridRow. Fortunately the DataGridRow is a visual ancestor of the DataGridRowHeader, so it's relatively simple to reach up there with a RelativeSource binding and get the values of the relevant properties: DataGridRow.IsEditing and DataGridRow.IsNewItem.
I used <Label>New</Label> etc. as an arbitrary stand-in for whatever content you want to use.
<DataGrid
ItemsSource="{Binding Rows}"
>
<DataGrid.RowHeaderStyle>
<Style
TargetType="{x:Type DataGridRowHeader}"
BasedOn="{StaticResource {x:Type DataGridRowHeader}}"
>
<!--
Empty content template for default state.
Triggers below replace this for IsNewItem or IsEditing.
-->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label></Label>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger
Binding="{Binding
IsEditing,
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"
Value="True"
>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label>Edit</Label>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger
Binding="{Binding
IsNewItem,
RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"
Value="True"
>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label>New</Label>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowHeaderStyle>
</DataGrid>

Textblock won't recieve trigger in HierachicalDataTemplate

I'm trying to get a TreeView to display items as a TextBlock, and then based on a boolean inside the data-bound object to either make the FontWeight Normal or Bold, pretty much the following:
<TreeView x:Name="TreeView" ItemsSource="{Binding Layers}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewModels:Layer}" ItemsSource="{Binding Path=Layers}">
<TextBlock Text="{Binding Path=Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowInPreview}">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The Setter outside the trigger actually works, when I set that one to "Bold", everything goes Bold right away. It's just the DataTrigger that never, well... triggers :P
The ItemSource implements INotifyPropertyChanged, and so does the Layer object on all properties (including the ShowInPreview).
I've tried all kinds of different setups I could find on the web (using Window.Resources, putting it in TreeView.ItemContainerStyle, etc. etc), so I'm completely at a loss right now!
Set the Value on your data trigger.
I dont know exactly where is your property, try something like this. I think, issue in binding:
<DataTrigger Binding="{Binding Path=DataContext.ShowInPreview, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}}}">

Conditional styling of an element in XAML

I am building a Windows phone 8 app that has a view model property of:
public bool IsReply {get; set;}
In my xaml code, I would like to distinguish two cases:
IsReply=True
<Grid Margin="0,0,0,0">
...
</Grid>
IsReply=False
<Grid Margin="40,0,0,0">
...
</Grid>
Basically, I would like to style the Grid element depending on the value of IsReply. I know that in WPF Style.Triggers exists, but apparently not in WP.
The solution I have right now is to have a duplicate copy of the entire grid code and set the visibility of each to a data converter. However, I feel this should be simpler to do.
The easiest way is to use a Style with Triggers:
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Margin" Value="40 0 0 0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsReply}" Value="True">
<Setter Property="Margin" Value="0 0 0 0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
You can bind the margin of the grid in your MVVM
<Grid Margin="{Binding margin}">
...
</Grid>
In your model
if(IsReply)
margin = new Thickness("0,0,0,0");
else
margin = new Thickness("40,0,0,0");
No need to create separate grids.
You can use DataTrigger, but you have to add these two references (right click on References in your project and AddReference/Assemblies/Extensions/ ... ).
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<Grid
Margin="0">
<i:Interaction.Triggers>
<ei:DataTrigger
Binding="{Binding Path=IsReply}"
Value="True">
<ei:ChangePropertyAction
PropertyName="Margin"
Value="0" />
</ei:DataTrigger>
<ei:DataTrigger
Binding="{Binding Path=IsReply}"
Value="False">
<ei:ChangePropertyAction
PropertyName="Margin"
Value="40,0,0,0" />
</ei:DataTrigger>
</i:Interaction.Triggers>
</Grid>

WPF View sets ViewModel properties to null on closing

I have an application where I'm displaying UserControls in a GroupBox. To display the controls, I'm binding to a property in the ViewModel of the main form, which returns a ViewModel to be displayed. I've got DataTemplates set up so that the form automatically knows which UserControl/View to use to display each ViewModel.
When I display a different UserControl, I keep the ViewModel of the previous control active, but the Views are discarded automatically by WPF.
The problem that I'm having is that when the view shuts down, any two way bindings to the properties in the ViewModel are immediately set to null, and so when I display the ViewModel again all of the values are just set to null in the UI.
I assume this is because as part of the View closing down it disposes and clears any values in the controls it contains, and since the bindings are in place they propagate down to the ViewModel as well.
DataTemplates in my resources
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<vw:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
<vw:SettingsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:JobListViewModel}">
<vw:JobListView />
</DataTemplate>
Code used to display user controls
<GroupBox>
<ContentControl Content="{Binding Path=RightPanel}" />
</GroupBox>
Example of a control that I'm binding in one of the Views:
<ComboBox Name="SupervisorDropDown" ItemsSource="{Binding Path=Supervisors}" DisplayMemberPath="sgSupervisor"
SelectedValuePath="idSupervisor" SelectedValue="{Binding Path=SelectedSupervisorID}" />
and the relevant ViewModel properties:
public ObservableCollection<SupervisorsEntity> Supervisors
{
get
{
return supervisors;
}
}
public int? SelectedSupervisorID
{
get
{
return selectedSupervisorID;
}
set
{
selectedSupervisorID = value;
this.OnPropertyChanged("SelectedSupervisorID");
}
}
Any idea on how to stop my Views nulling the values in my ViewModels? I'm thinking that maybe I need to set the DataContext of the View to null before it closes down, but I'm not sure how to go about that with the way things are currently binding.
I've found one possible solution, but I really don't like it.
It turns out the DataContext IS being set to null already, but that doesn't help. It happens before the property is set to null. What appears to be happening is that the data bindings aren't being removed before the UserControl/View disposes of itself, and so the null value propagates down when the control is removed.
So when the DataContext changes, if the new context is null then I remove the relevant bindings on the ComboBox, as follows:
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
SupervisorDropDown.ClearValue(ComboBox.SelectedValueProperty);
}
}
I'm not a big fan of this method, because it means I have to do remember to do it for every databound control I use. If there was a way I could just have every UserControl just remove their bindings automatically when they close that would be ok, but I can't think of any way to do that.
Another option might be to just restructure my app, so that the Views don't get destroyed until the ViewModels do - this would sidestep the problem entirely.
When I display a different
UserControl, I keep the ViewModel of
the previous control active, but the
Views are discarded automatically by
WPF.
The problem that I'm having is that
when the view shuts down, any two way
bindings to the properties in the
ViewModel are immediately set to null,
and so when I display the ViewModel
again all of the values are just set
to null in the UI.
I'm no expert on either WPF or MVVM, but something about this doesn't sound right. I have trouble believing that WPF disposal of the view is causing your problem. At the very least, in my limited experience I've never had anything like that happen. I suspect the culprit is either code in the view-model or the code swaping out which view-model is used for the datacontext.
After trying to stop the null setting by various means, I gave up and instead got it working as follows. I made the ViewModel read-only before closing its view. I accomplish this in my ViewModelBase class, where I added a IsReadOnly boolean property. Then in ViewModelBase.SetProperty() (see below) I ignore any property changes when IsReadOnly is true.
protected bool SetProperty<T>( ref T backingField, T value, string propertyName )
{
var change = !IsReadOnly && !EqualityComparer<T>.Default.Equals( backingField, value );
if ( change ) {
backingField = value;
OnPropertyChanged( propertyName );
}
return change;
}
It seems to be working like this, although I'd still love to know a better solution.
I had the same problem. What worked for me was removing UpdateSourceTrigger=PropertyChanged from my SelectedValueBindings. PropertyChanged UpdateSourceTriggers seem to fire on bound properties of closing views when you use that pattern:
<!--Users DataGrid-->
<DataGrid Grid.Row="0" ItemsSource="{Binding DealsUsersViewSource.View}"
AutoGenerateColumns="False" CanUserAddRows="True" CanUserDeleteRows="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<DataGrid.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#FFC5D6FB"/>
</DataGrid.Resources>
<DataGrid.Columns>
<!--Username Column-->
<DataGridComboBoxColumn
SelectedValueBinding="{Binding Username}" Header="Username" Width="*">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding DataContext.DealsUsersCollection.ViewModels,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="SelectedValuePath" Value="Username"/>
<Setter Property="DisplayMemberPath" Value="Username"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding DataContext.BpcsUsers,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="SelectedValuePath" Value="Description"/>
<Setter Property="DisplayMemberPath" Value="Description"/>
<Setter Property="IsEditable" Value="True"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<!--Supervisor Column-->
<DataGridComboBoxColumn
SelectedValueBinding="{Binding Supervisor}" Header="Supervisor" Width="*">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding DataContext.DealsUsersCollection.ViewModels,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="SelectedValuePath" Value="Username"/>
<Setter Property="DisplayMemberPath" Value="Username"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding DataContext.BpcsUsers,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="SelectedValuePath" Value="Description"/>
<Setter Property="DisplayMemberPath" Value="Description"/>
<Setter Property="IsEditable" Value="True"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<!--Plan Moderator Column-->
<DataGridCheckBoxColumn Binding="{Binding IsPlanModerator}" Header="Plan Moderator?" Width="*"/>
<!--Planner Column-->
<DataGridCheckBoxColumn Binding="{Binding IsPlanner}" Header="Planner?" Width="*"/>
</DataGrid.Columns>
</DataGrid>
Container View:
<!--Pre-defined custom styles-->
<a:BaseView.Resources>
<DataTemplate DataType="{x:Type vm:WelcomeTabViewModel}">
<uc:WelcomeTabView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:UserSecurityViewModel}">
<uc:UserSecurityView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PackItemRegisterViewModel}">
<uc:PackItemsRegisterView/>
</DataTemplate>
</a:BaseView.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<TabPanel Grid.Column="1" Grid.Row="1">
<TabControl TabStripPlacement="Top" ItemsSource="{Binding TabCollection}" SelectedIndex="{Binding SelectedTabIndex}"
DisplayMemberPath="DisplayName" MinWidth="640" MinHeight="480"/>
</TabPanel>
</Grid>
Container ViewModel:
TabCollection.Add(new WelcomeTabViewModel());
TabCollection.Add(new UserSecurityViewModel(_userService, _bpcsUsersLookup));
TabCollection.Add(new PackItemRegisterViewModel(_packItemService, _itemClassLookup));
SelectedTabIndex = 0;
Set the UpdateSourceTrigger explicit to LostFocus
If the view is closing and sets its data to null, it has no effect on your data in the viewmodel.
<ComboBox Name="SupervisorDropDown" ItemsSource="{Binding Path=Supervisors}" DisplayMemberPath="sgSupervisor"
SelectedValuePath="idSupervisor"
SelectedValue="{Binding Path=SelectedSupervisorID, UpdateSourceTrigger=LostFocus}" />

Categories