I have in my ParentView(DashboardConsultants) a gridview which shows a custom tooltip when the user's mousepointer is hovered over a cell. The tooltip show a View (AgreementDetails_View) which shows information of the Agreement binded to that cell. I will show the code I have now so you can better understand my question:
DataGrid Cell in ParentView:
<DataGridTextColumn Header="Okt" Width="*" x:Name="test">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Tag" Value="{Binding Months[9].AgreementID}"/>
<Setter Property="Background" Value="White" />
<Setter Property="DataGridCell.ToolTip" >
<Setter.Value>
<v:UC1001_AgreementDetails_View Background="#FFF" Opacity="0.88" />
</Setter.Value>
</Setter>
My ChildView:
public UC1001_DashBoardConsultants_View(UC1001_DashboardConsultantViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
In the ViewModel, I have following method to get the right Agreement from the database:
private void GetRefData()
{
UC1001_ActiveAgreementArguments args = new UC1001_ActiveAgreementArguments();
args.AgreementID = 3;
DefaultCacheProvider defaultCacheProvider = new DefaultCacheProvider();
if (!defaultCacheProvider.IsSet("AgrDet:" + args.AgreementID))
{
ConsultantServiceClient client = new ConsultantServiceClient();
AgreementDetailsContract = client.GetAgreementDetailsByAgreementID(args);
defaultCacheProvider.Set("AgrDet:" + args.AgreementID, AgreementDetailsContract, 5);
}
else
{
AgreementDetailsContract = (UC1001_ActiveAgreementContract)defaultCacheProvider.Get("AgrDet:" + args.AgreementID);
}
}
As you can see for now, the method always calls the same Agreement, (I did that for testing purposes) but now I want the Agreement which ID is specified in the DataGrid Cell Tag (in this example it's the Months[9].AgreementID).
I can give it to the ViewModel in my Child View's constructor, but I don't think that it's allowed due to the MVVM Pattern (or is it allowed?).
So my question is: How can I pass the AgreementID specified in my ParentView to the ChildView's ViewModel to get the right data for the ChildView?
Ofcourse, more information/code/clarification can be happily provided, just ask :)
Thanks in advance!
Not sure that I got question in righ way but my feelings like you need to use Commands instead of introducing tied coupling by passing back reference to parent itself
Personally, I feel that WPF Views should be nothing more than a pretty reflection of the ViewModel. So the View should not actually be passing any data to the ViewModels - instead it should be reflecting the ViewModel's data.
In your case, I would attach a property to the object that is displayed in each DataGrid Row. For example, if your DataGrid contained Agreement objects, I would ensure that each Agreement object had a property called AgreementDetails which can be viewed from the ToolTip
It's pefectly legal and valid to pass in that ID eitehr via a constructor or through a property. I'm not sure from your code, but if your parent is the one with access to your model, you can also pass teh model into your view model (i.e. via constructor, property, or method).
One thing I often do in this case is to add a property such as the following in the ViewModel of my Parent:
object ActiveItem {get;set;}
I then bind that ActiveItem to the ActiveItem in my grid.
<DataGrid SelectedItem="{Binding ActiveItem}">
</DataGrid>
Related
I have a GridView inside a ListView in a WPF application. Upon clicking the column header the list items get sorted alphabetically.
XAML:
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="200">
<GridViewColumn.HeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="cal:Message.Attach" Value="[Event Click]=[Action Sort($dataContext, 'Name')]"/>
</Style>
</GridViewColumn.HeaderContainerStyle>
</GridViewColumn>
And here's the sorting method in my ViewModel:
public void Sort(ShellViewModel dataContext, string propertyName)
{
// sorting logic
}
That works perfectly fine. But instead of passing 'Name' (or whatever the header is called), I'd like that to be a property.
I've tried Value="[Event Click]=[Action Sort($dataContext, $eventArgs.PropertyName)]" but that doesn't work and returns null.
You might want to consider changing the listview to a datagrid.
A datagrid has column click sorting built in. No code would be necessary. You'd need to change your markup to a datagrid, which might be non trivial of course. There may be some reason to pick listview instead of datagrid.
If that doesn't suit then the current approach looks like it is going to be tricky.
Looking at the msdn article about sorting a listview. That handles the event.
The piece of code finding the propertyname is:
var headerClicked = e.OriginalSource as GridViewColumnHeader;
.....
var columnBinding = headerClicked.Column.DisplayMemberBinding as Binding;
var sortBy = columnBinding?.Path.Path ?? headerClicked.Column.Header as string;
You might be able to get the binding from $eventArgs. if you use enough dots.
However, I think you would need to get the path out the binding in code.
I personally wouldn't be so keen on a viewmodel doing that sort of manipulation.
I'd suggest a behaviour if you want to encapsulate it or "just" do the sorting entirely in the view. Very much like
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/how-to-sort-a-gridview-column-when-a-header-is-clicked?view=netframeworkdesktop-4.8
Maybe you don't want the up down arrows and some other aspects.
If the viewmodel needs to know what the user picked you could add an attached property and bind that to the viewmodel. Setcurrentvalue on that property in the view.
Similarly, you could make that twoway. Handle change of the dependency property and set sort if the viewmodel needs to initially set sort dynamically.
Alternatively, extend the attached property concept. Put a sort togglebutton in the headers and bind to an observabledictionary. One entry per column. Handle property changed in the value of the observabledictionary and apply sort.
I've been slowly playing around with MVVM and the concept makes since. I have an application window and 2 user controls with viewModels attached to each. I want to click on a button within the first user control and get taken to the second user control.
I've looked at a few tutorials such as:
Example 1Example 2
Both those change screens from within the main window view model but I'd like to do it within the user control itself. Is it possible to either pass a command to change windows back to the main application view model, or have the user control model change the view on button click.
Edit: I figure I need to pass it as a command but I'm not sure how to pass which view I want along with it.
In MVVM, commands generally work at ViewModel level and have nothing to do with (or have any knowledge of) the View layer. To achieve what you have described, create a public property that controls the type of View you want to see. For example (using MVVM Light):
class YourViewModel : ViewModelBase
{
public string ActiveView
{
get { return _ApplicationMessage; }
//Note that this setter performs notification
set { Set(ref _ApplicationMessage, value); }
}
private RelayCommand<string> _SetViewCommand = null;
public RelayCommand<string> SetViewCommand
{
get
{
if (_SetViewCommand == null)
{
_SetViewCommand = new RelayCommand<string>((v) =>
{
ActiveView = v;
}
}
return _SetViewCommand;
}
}
}
As you see, your commands will simply manipulate the value of the public property to the appropriate value. Now your View can build on top of this property and use DataTriggers to load proper Content:
<Button Content="Normal" Command="{Binding SetViewCommand}" CommandParameter="Normal" />
<Button Content="Edit" Command="{Binding SetViewCommand}" CommandParameter="Edit" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ActiveView}" Value="Normal">
<Setter Property="Content">
<Setter.Value>
<UserControl1 />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ActiveView}" Value="Edit">
<Setter Property="Content">
<Setter.Value>
<UserControl2 />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Edit
Answering your comment:
view doesn't really interact with the viewModel: That's incorrect. View always interacts with the VM, it is the VM that doesn't know anything about the view. About your confusion, understand that you don't need to create one VM per user control. A VM is created against a View, not a control. So you'll probably have a single ViewModel named MainVM, exposing all the public properties that your view will bind to. Each User Control can then use only the properties that it is interested in. A trigger can then switch the view from one User Control to another when a particular property of the underlying VM is changed. The new User Control will then show correct data since it will be bound to the VM properties that it wants to work with.
Just remember that the VM is not interested in (nor have a way of) knowing how view is using it. It just needs to maintain its state correctly. View can then respond to the state changes and update itself accordingly.
I am developing my first WPF Application, so I am not seasoned at all on WPF. I have the following situation:
I have a window that has a DataGrid control. The constructor of the window receives a DataTable as input. The Window class is as follows:
public partial class Project_Status_Window : Window {
//Required for the datacontext
public DataTable status { get; }
public Project_Status_Window(DataTable status) {
InitializeComponent();
this.DataContext = this;
this.status = status;
this.Show();
}
}
In order to display the table I am binding the status table in my XAML description of the window. As follows:
<DataGrid x:Name="Status_Table_Viewer" ItemsSource="{Binding status.DefaultView}"
AlternatingRowBackground="LightBlue" AlternationCount="1"
SelectionMode="Extended" SelectionUnit="Cell" ScrollViewer.CanContentScroll="True">
Now I want to change the background of the cells in one column (preferably selecting the column by name). However the background should be changed only if the value in the cells is contained in a list that is populated through a method that in turn invokes a database query. Since this is presentation logic I would like to keep it on the XAML side. I have been researching a lot and cannot find a similar case, though I believe that this can be done with DataTriggers, so I have the following code in XAML:
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Style.Triggers>
<DataTrigger Value="True"> I have no Idea what to write in this tag
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
Let's assume that the window will have a method that receives a string as input and returns true or false depending on whether the string is missing in the database or not. The method will be the following:
public bool isMissingMat(string input)
I don't know how to access the data in a named column of my DataTable, pass the data to my custom method and then act on the formatting of the DataGrid based on the value of my method.
I understand what you want to achieve. Unfortunately, getting formatting working for a DataGrid is devilishly difficult and cannot be explained here to you step by step. Better just read my article CodeProject: Guide to WPF DataGrid formatting using bindings. It gives a lot of background information which you even cannot find on MSDN :-(
It has also a sample how you can change the background colour of a row based on business logic data, precisely what you need.
You wrote: "Since this is presentation logic I would like to keep it on the XAML side." That is not a good idea. XAML is good at defining static content, but horribly bad at doing simple logical stuff, like it can even not do addition or if-statements or debugging or ... Your life is MUCH easier, when you do dynamic stuff in code behind. Again, see my article to get a feeling what you should do in XAML and what is better done in code behind.
I have my custom toolbar control with DependencyProperty IsBusy
Here is how I use it:
<Controls:myToolbar
Grid.ColumnSpan="5" Mode="DataEntry"
Status="{Binding State, Converter={StaticResource ViewEditingStateToToolbarStateConverter}}"
IsBusy="{Binding IsBusy}"/>
By convention all my VM's inherit from base VM and have IsBusy property.
So, I KNOW that this property will always be available on VM.
Now I have another 4 properties like this. Instead of adding them to XAML on all my views I want to know how to bind to this IsBusy automatically inside control's code so I don't have to bind in XAML?
EDIT
Actually, I found answer to my question: Silverlight: Programmatically binding control properties
Now, my question is:
Is it correct to apply this binding in constructor like this?
public myToolbar()
{
this.DefaultStyleKey = typeof(myToolbar);
var binding = new Binding("IsBusy") { Mode = BindingMode.TwoWay };
this.SetBinding(IsBusyProperty, binding);
}
Should I check if XAML binding (another binding) exist to this property and not bind? It works either way but I wonder if it's bad for performance, smells, etc?
What about doing this in onApplyTemplate. Is that better way?
if (GetBindingExpression(IsBusyProperty) == null)
{
var binding = new Binding("IsBusy") { Mode = BindingMode.TwoWay };
this.SetBinding(IsBusyProperty, binding);
}
It would be bad if you tried to use this control with a view model which doesn't have the IsBusy property, but even then you'll receive just a debug warning in the output window, nothing to worry about.
As to the place of the binding, the constructor is appropriate if the dependency property which you are binding to doesn't perform any actions inside its callback.
But if the property changed callback tries to call such functions as GetTemplateChild and retrieve inner controls - then you should move the binding to the OnApplyTemplate functions, because only there you can be assured that inner controls exist.
By the way, if your dependency proeprty doesn't have a property changed callback and is used only in the control template like {TemplateBinding IsBusy}, you can replace this line by {Binding IsBusy}. Something like this, either by using binding or data triggers:
<ControlTemplate TargetType="{x:Type Controls:myToolbar}">
<Grid>
<ContentControl x:Name="content" ... />
<ProgressBar x:name="progress" ... />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsBusy}" Value="True">
<Setter TargetName="progress" Property="Visibility" Value="Visible" />
</DataTrigger>
The idea is simple: TemplateBinding is applied to dependency properties of the control, whereas Binding is applied to properties of the DataContext object or the view model and they can coexist without problems.
I've hit a bit of a dead end in trying to figure this one out... Using the MVVM pattern in WPF, our C# Model fires an event to say something has happened. I want to be able handle that event in my ViewModel and then either kick of a storyboard or change the visibility of a hidden panel on the current Xaml Page. This has to be handled with no Code Behind.
I can sync for the event in my ViewModel, update a property to say what the name of that event is and fire a NotifyPropertyChanged even but how do I get that to either kick off a storyboard or map to a boolean true/false on the Visibility property of my Grid? The property I bind to hs to be the event name as different grids may be shown based on different events so I need a way of mapping this to a boolean. However the ideal solution would be to kick off a storyboard. I've looked at DataTriggers but they all seem to be linked to styles and not to actual pages.
Any ideas of how I can achieve this?
Thanks!
I've used this in the past to kick off a storyboard in code-behind
Storyboard animation = (Storyboard)this.FindResource("ShowPanelStoryboard");
animation.Begin();
This code goes behind the View, not in the ViewModel. Personally, I don't mind some code behind my View providing it is only related the View. In the project I used this in, I added a listener to the VisibilityChanged event and when it got changed to Visible, I ran the storyboard.
As for showing your popup, there's a few ways. One of my favorites was just adding an IsPopupShown property to the ViewModel, binding my panel's visibility to it, and setting it to true anytime the popup should be shown. The ViewModel then handles the events that trigger the popup being shown or not.
An alternative as suggested by Dave White is to use a converter. If your value is not always true/false then you could create a converter that checks if a bound value is equal to the ConverterParameter, and return a Visibility value.
From your comment, it seems to me like what you may want to do is expose an Event property of type object in your view model. When the view model receives an event, it sets Event to an object of a type appropriate for that event. In your XAML, you have this:
<ContentControl Content="{Binding Event}"/>
and in the resource dictionary define a DataTemplate for each specific type of event you want to display. If Event is null, nothing gets displayed. If Event contains an object that you've defined a DataTemplate for, it gets displayed using that template.
Yes, you'll need to create a class for each type of event (if you don't already have one).
Another way is to implement the poor man's template selector:
<TextBlock Text="This is displayed if Foo contains 'BAR'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAR">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="This is displayed if Foo contains 'BAZ'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAZ">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
It's kind of stupidly verbose, but it's an easy way to handling a lot of mutually-exclusive display options.
Bind the Visibility property on your grid, in Xaml, to the boolean property on your ViewModel.
<Grid Visibility="{Binding Path=VisiblePropertyOnViewModel}">
Now do whatever you need in your ViewModel and set the property. As long as it does INotifyPropertyChanged or is a DependencyProperty, it will work.
I'd have to do more digging to figure out how to kick off a Storyboard, but I have no doubt it would be almost as easy. Storyboards can be kicked off by PropertyTriggers as well I believe. I'll leave this to get you started.