How to get my original ViewModel? - wpf

I created a User Control that has ViewModelA() as its ViewModel then inside my View, there's a StackPanel that uses ViewModelA.Data as DataContext.
My problem is inside this StackPanel, I have a button that needs to implement my created ICommand inside ViewModelA(). How can I do that?
Is there anything like <Button DataContext="DataContext.Parent" /> or something like that?
Here's how I implemented my ViewModel and View:
App.xaml
<DataTemplate DataType="{x:Type vm:ViewModelA}">
<vw:ViewA />
</DataTemplate>
ViewA.xaml (where the button inside the stack panel should implement the ICommand)
<StackPanel x:Name="RightPaneDetails"
Grid.Column="1"
Margin="15,0,0,30"
DataContext="{Binding Data}">
<!-- Some controls goes here that binds to ViewModelA.Data properties -->
<StackPanel Orientation="Horizontal">
<Button DataContext={Binding} Command="{Binding LookupCommand}" /> <!-- This button should implement the ViewModelA.LookupCommand -->
</StackPanel>
</StackPanel>
TIA
PS, ViewModelA.Data is my model.

It looks like you have set DataContext as ViewModelA in your UC. So, you can use
<Button DataContext={Binding} Command="{Binding DataContext.LookupCommand, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" />
It can also be written as :
<Button DataContext={Binding DataContext, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor} } Command="{Binding LookupCommand}" />

Although #AnjumSKhan's solution is correct, I would like to propose alternative solution:
<StackPanel x:Name="RightPaneDetails"> <!-- do not set bind datacontext here -->
<!-- Some controls goes here that binds to ViewModelA.Data properties, e.g: -->
<TextBlock Text="{Binding Data.SomeProperty}" />
<!-- If there too many elements bound to Data, you can group them in stackpanel -->
<StackPanel Orientation="Horizontal">
<!-- This button is databound to ViewModelA.LookupCommand
DataContext doesn't have to be set, since it's inherited from parent. Actually entire stackpanel is redundant here-->
<Button Command="{Binding LookupCommand}" />
</StackPanel>
</StackPanel>
As you can see I just avoided setting DataContext on the parent RightPaneDetails so the children can easily access both ViewModelA.Data's properties (TextBlock) and ViewModelA's properties (Button)
Few other notes:
Notice, that instead of
RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}
you can write just
RelativeSource={RelativeSource AncestorType=UserControl}
Alternative solution to RelativeSource is ElementName:
<StackPanel x:Name="Root">
<StackPanel DataContext="{Binding Data}">
<Button Command="{Binding DataContext.LookupCommand, ElementName=Root}" />
I preffer this because existence of "Root" element is checked at compile time and it is more readable.
Avoid binding DataContext and another Property on the same element, e.g:
<Button DataContext="{Binding....}" Command="{Binding ...}" />
There is no guarantee, which binding will be evaluated first. You can, however, set IsAsyc=True to the second binding Command={Binding ..., IsAsync=True} to ensure it will be evaluated later than non async bindings

Related

DataTemplate Binding depending on property type and with working property Binding

I check those articles about doing DataTemplate :
WPF DataTemplate Binding
WPF DataTemplate and Binding
WPF DataTemplate Textblock binding
and thoses about DataTemplate depending on property type :
WPF DataTemplate Binding depending on the type of a property
Dynamically display a control depending on bound property using WPF
I'm trying to display a property with different controls depending of the property value. I have this Xaml that is partialy working. I have 2 problems :
The property is displaying with the right control, but when I set the value it doesn't go back to the property. Means the "set" of My property is not call (but was before I creates the DataTemplate). I detect that the problem about setting the property is about the ="{Binding Path=.}" but I cannot find the solution to set it otherwise.
Also To be able to make it work, I had to "isolate" the Value into a single ViewModel so that the DataTemplate doesn't affect all the other control.
Can you help me find betters solutions to resolves those 2 problems?
Here is the xaml code of my View linked with MyContainerViewModel that has a "ChangingDataType" :
<UserControl >
<UserControl.Resources>
<!-- DataTemplate for strings -->
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=.}" HorizontalAlignment="Stretch"/>
</DataTemplate>
<!-- DataTemplate for bool -->
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=.}" />
</DataTemplate>
<!-- DataTemplate for Int32 -->
<DataTemplate DataType="{x:Type sys:Int32}">
<dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="d" MaskType="Numeric" HorizontalAlignment="Stretch"/>
<!--<Slider Maximum="100" Minimum="0" Value="{Binding Path=.}" Width="100" />-->
</DataTemplate>
<!-- DataTemplate for decimals -->
<DataTemplate DataType="{x:Type sys:Decimal}">
<!-- <TextBox Text="{Binding Path=.}" MinWidth="50" HorizontalAlignment="Stretch" />-->
<dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="f" MaskType="Numeric" HorizontalAlignment="Stretch" />
</DataTemplate>
<!-- DataTemplate for DateTimes -->
<DataTemplate DataType="{x:Type sys:DateTime}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</DataTemplate.Resources>
<DatePicker SelectedDate="{Binding Path=.}" HorizontalAlignment="Stretch"/>
</DataTemplate>
</UserControl.Resources>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</UserControl>
More informations about 2 :
I wanted to have in a view a label and a property that changes depending of the object. Something like this :
<UserControl>
<UserControl.Resources>
<!-- ...DataTemplate here... -->
</UserControl.Resources>
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</StackPanel>
</UserControl>
But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo". So I had to create another view that contains the DataTemplate and MyChangingProperty so that the label Allo would not be affected. But the extra View created just for one property is kind of ugly to me, I'm sure there is a better way to isolate the DataTemplate so it can apply only to one UIControl.
<UserControl >
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyContainerViewModel}"/>
</StackPanel>
</UserControl>
Note : MyContainerViewModel here is linked with the first view described.
Thanks in advance!
One possible solution would be to use a DataTemplateSelector. You cannot bind primitive types using two way bindings because that would have to be somehow by reference via the DataTemplate which I think is not supported by WPF.
The DataTemplateSelector now selects the right DataTemplate based on the property type and searches for the right DataTemplate in the resources by name. This also solves your problem that your DataTemplates interacted with the Label.
So first you need to define a DataTemplateSelector that changes the DataTemplate based on the type of the property:
public class MyDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var fe = (FrameworkElement)container;
var prop = (item as MyViewModelType)?.MyChangingProperty;
if (prop is string)
return fe.FindResource("MyStringDT") as DataTemplate;
else if (prop is bool)
return fe.FindResource("MyBoolDT") as DataTemplate;
// More types...
return base.SelectTemplate(item, container);
}
}
Then you need to change the UserControl like this:
<UserControl>
<UserControl.Resources>
<local:MyDataTemplateSelector x:Key="MyDTSelector" />
<!-- DataTemplate for strings -->
<DataTemplate x:Key="MyStringDT">
<TextBox Text="{Binding MyChangingProperty, Mode=TwoWay}"
HorizontalAlignment="Stretch"/>
</DataTemplate>
<!-- DataTemplate for bool -->
<DataTemplate x:Key="MyBoolDT">
<CheckBox IsChecked="{Binding MyChangingProperty, Mode=TwoWay}" />
<!-- More DataTemplates... -->
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyContainerViewModel}"
ContentTemplateSelector="{StaticResource MyDTSelector}" />
</StackPanel>
</UserControl>
You can find a bit more information regarding the DataTemplateSelector here.
You can of course also set a DataType on this new DataTemplates but it isn't required because the x:Key makes them unique anyway. But if you want then it has to look like this:
<DataTemplate x:Key="MyStringDT" DataType="{x:Type local:MyViewModelType}">
In my opinion, the previously posted answer is overkill. While a DateTemplateSelector is a useful thing to know about, it seems unnecessary to me in this scenario.
But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo".
The reason it affects the Label object is that the Label object is a ContentControl, and so does the same template-matching behavior for content types as your own ContentPresenter element does. And you've set the content of the Label object to a string value. But you can put anything you want as the content for it.
The way to fix the undesired effect is to intercept that behavior by changing the content from a string object to an explicit TextBlock (the control in the template that a string object normally gets assigned). For example:
<UserControl>
<UserControl.Resources>
<!-- ...DataTemplate here... -->
</UserControl.Resources>
<StackPanel>
<Label>
<TextBlock Text="Allo"/>
</Label>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</StackPanel>
</UserControl>
In that way, you bypass the template-finding behavior (since TextBlock doesn't map to any template and can be used directly), and the content for the Label will just be the TextBlock with the text you want.
This seems like a lot simpler way to fix the issue, than either to create a whole new view or to add a DataTemplateSelector.

WPF: Set UserControl DataContext

I have the following MainWindow that lays out a left side navigation panel and a right side display area (both of these are UserControls).
Can someone explain how to assign the DataContext of the navigation panel (LinksView.xaml) to that of LinksViewModel.cs. I would like to bind a Command (BtnCompanyClickCommand) to the button and define BtnCompanyClickCommand in LinksViewModel.cs.
I have tried various methods that I found on StackOVerflow to set the DataContext but none of these solutions seem to work (binding RelativeSource, naming view and binding to name, etc.).
MainWindow.xaml
<StackPanel Orientation="Horizontal">
<vw:LinksView DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
<ContentControl Content="{Binding CurrentUserControl}" />
</StackPanel>
LinksView.xaml
<StackPanel Orientation="Vertical">
<Button Content="Company" Width="75" Margin="3" Command="{Binding ElementName=Links,Path=BtnCompanyClickCommand}" />
</StackPanel>
FormsDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SidekickAdmin.ViewModel"
xmlns:vw="clr-namespace:SidekickAdmin.View">
<DataTemplate DataType="{x:Type vm:CompanySummaryViewModel}">
<vw:CompanySummaryView>
<ContentControl Content="{Binding }" />
</vw:CompanySummaryView>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:LinksViewModel}">
<vw:LinksView />
</DataTemplate>
</ResourceDictionary>
EDIT
So I finally came across this explanation of how to set the DataContext of a UserControl which has to be done on the first child item of the UserControl.
Here is the modified LinksView.xaml that works.
<StackPanel Orientation="Vertical">
<StackPanel.DataContext>
<vm:LinksViewModel /> <!-- Bind the items in StackPanel to LinksViewModel -->
</StackPanel.DataContext>
<Button Content="Company" Width="75" Margin="3" Command="{Binding BtnCompanyClickCommand}" />
</StackPanel>
However, I am still not clear on why I have to set the DataContext of the child element and not the UserControl and why the DataTemplate for LinksView (set in FormsDictionary.xaml) doesn't tie into the DataContext of LinksViewModel. Any explanation would be appreciated.
First you have to refer to your DataContext (LinksViewModel.cs) in your XAML code.
You can do that either by directly instantiating it or use a ResourceDictionary. In the latter case you instantiate your DataConext either inside some .cs file or inside the ResourceDictionary .xaml file and store it in a named ResourceDictionary where you can find the reference later.
Second you simply have to associate the DataContext property of a View element like your LinksView.xaml with the corresponding DataContext.
This is pretty high-level and without any code but that's the basic idea behind it.
there should be an instance of LinksViewModel in MainWindowViewModel:
MainWindowViewModel.cs:
class MainWindowViewModel
{
public MainWindowViewModel()
{
LinksVM = new LinksViewModel();
}
public LinksViewModel LinksVM { get; private set; }
}
MainWindow.xaml
<StackPanel Orientation="Horizontal">
<vw:LinksView DataContext="{Binding LinksVM}"/>
<ContentControl Content="{Binding CurrentUserControl}" />
</StackPanel>
LinksView.xaml
<StackPanel Orientation="Vertical">
<Button Content="Company" Width="75" Margin="3" Command="{Binding BtnCompanyClickCommand}" />
</StackPanel>
since LinksView is explicitly created in MainWindow, there is no need in DataTemplate - it can be removed
<DataTemplate DataType="{x:Type vm:LinksViewModel}">
<vw:LinksView />
</DataTemplate>

Binding properties through DataTemplates and ContentControl

I liked this answer, and it almost fit me.
But, how can I achieve this if my DataTemplate is in a external ResourceDictionary?
I'm using Prism and I provide the DataTemplates (for generic CRUD views) by each module, by using files like this:
<ResourceDictionary ... some hidden ns here ... >
<DataTemplate DataType="{x:Type model:Operation}">
<vw:OperationView />
</DataTemplate>
<DataTemplate DataType="{x:Type model:Customer}">
<vw:CustomerView />
</DataTemplate>
</ResourceDictionary>
Then I use this answer to merge the ResourceDictionaries into the Shell app and I have a default CRUD view which has that code:
<ContentControl Content="{Binding MyGenericObject}" />
That ContentControl automatically pull the correct view. It's working fine, but I want to know bind the property of the objects in each view.
That's a sample of these views (OperationView.xaml):
<UserControl x:Class="TryERP2.Cadastro.View.OperationView"
... some hidden NS ... >
<StackPanel>
<Label Content="Id" />
<TextBox Text="{Binding ????WHAT????}" />
<Label Content="Description" />
<TextBox Text="{Binding ????WHAT????}" />
</StackPanel>
</UserControl>
How can I bind these properties?
Since the DataContext behind OperationView will be an object of type Operation, then you simply bind to whatever property on Operation you want
<!-- DataContext will be model:Operation per your DataTemplate -->
<UserControl x:Class="TryERP2.Cadastro.View.OperationView"
... some hidden NS ... >
<StackPanel>
<Label Content="Id" />
<TextBox Text="{Binding Id}" />
<Label Content="Description" />
<TextBox Text="{Binding Description}" />
</StackPanel>
</UserControl>
The DataContext in the UserControl is your model object, so you can directly bind to its properties like this:
Text="{Binding SomeProperty}"
(If only a path is specified the binding is relative to the DataContext, note that in the answer you linked the intention was to have a TwoWay binding on the DataContext itself which was a primitive string, this cannot be done using a simple binding like {Binding .}, a property path targeting an actual property needs to be specified)

UI object, on samelevel of XAML Tree, as CommandParameter

I have an XAML tree as follows:
<Window>
<Grid>
<DockPanel>
<DataGrid>
<DataGrid.Resources>
<CheckBox Command="{Binding Command}" CommandParameter="??" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<DataChart>
</DataChart>
</stackPanel>
</DockPanel>
</Grid>
</Window>
I want to have DataChart object as CommandParameter on ViewModel from a Command on DataGrid.
My Findings:
I'm getting DockPanel object as CommandParameter, then I have to apply method FindName("") to get the DataChart. And do further modifications.
But I want the DataChart object directly, to avoid TypeCasting or searching down the Tree.
You can keep datachart as a named resource in your DockPanel resources and use static resource binding to command parameter. Then use ContentControl to host it.
like this...
<DockPanel>
<DockPanel.Resources>
<DataChart x:Key="MyDataChart">
</DataChart>
</DockPanel.Resources>
<DataGrid>
<DataGrid.Resources>
<CheckBox
Command="{Binding Command}"
CommandParameter="{StaticResource MyDataChart}" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<ContentControl Content="{StaticResource MyDataChart}"/>
</stackPanel>
</DockPanel>
Hoping that you wont use same MyDataChart to host to another area (as that would result in "visual tree parent disconnect" error).
Although I must ask you this... why is there a lonely CheckBox in your DataGrid resources?
Also your's and mine solution breaks MVVM because we are supplying a UI control (Chart control) to a View Model.

Pattern for working with a form in Silverlight 4? (How to get references to XAML elements)

I have a form:
<StackPanel Orientation="Horizontal" Visibility="{Binding Editable, Converter={StaticResource visibilityConverter}}"
ToolTipService.ToolTip="Add new topic to this group">
<sdk:AutoCompleteBox Width="160" ItemsSource="{Binding ElementName=LayoutRoot, Path=DataContext.TopicNames}" />
<Button Click="addTopicButton_Click">
<Image Source="Images/appbar.add.rest.png" />
</Button>
</StackPanel>
This form appears in a DataTemplate for an ItemsControl. I'm not sure what the best way is to get the data from the AutoCompleteBox when the button is clicked. I can't give the elements x:Name attributes, because they're in a template (right?).
How can I get around this? The Click event will give me the Button, but I need a reference to the text box. Use the Button's parent, then look through the children for the Textbox? If I factored this out into its own UserControl, I could set x:Name values, but I'd rather not do that.
Any other ideas?
Update: Here is another example of such a problem:
<ListBox x:Name="topicList"
ItemsSource="{Binding Id, Converter={StaticResource topicGroupIDConverter}}"
SelectionChanged="ListBox_SelectionChanged"
HorizontalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"
Width="150"
VerticalAlignment="Center"
ToolTipService.ToolTip="{Binding Description}"
ToolTipService.Placement="Right" />
<Button ToolTipService.ToolTip="Remove this topic from this group"
Visibility="{Binding ElementName=topicList,
Path=DataContext.Editable,
Converter={StaticResource visibilityConverter}}"
Click="removeTopicButton_Click"
HorizontalAlignment="Right"
Margin="10,0">
<Image Source="Images/appbar.cancel.rest.png" />
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When the button is clicked, I want to access topicList.DataContext. However, topicList itself is a DataTemplate in an ItemsControl, so I can't access it using its name from code-behind. How else can I do this?
You can add a property, say SelectedItemInAutoCompleteBox, to your presenter, and then can bind it to the SelectedItem property of AutoCompleteBox, using Mode=TwoWay, like this,
<sdk:AutoCompleteBox SelectedItem="{Binding Path=DataContext.SelectedItemInAutoCompleteBox, Mode=TwoWay}" ... />
You may try the same approach with Text property of AutoCompleteBox, also. See if it solves your problem.:-)
You have several choices:
If you're on Silverlight 5, use the AncestorBinding
Otherwise, use a Silverlight 4 AncestorBinding hack (it doesn't look pretty)
Or you could try DataContextProxy, which stores the DataContext in a resource so that it is accessible. Note: you should set the DataContextProxy as a Resource of topicList ListBox, not the UserControl as in Dan Wahlin's example.

Resources