How to bind to element inside contentcontrol in WPF - c#

I'm relativly new to WPF and currently working on a prototype. In this project i use contentcontrols and custom usercontrols quite a bit.
The structure is basically:
MainWindow -> Ribbon + TabControl of MainViews
MainView -> ContentView + EditPanel
EditPanel -> DetailInfo + Editor
Now this editor contains a FrameworkElement that exposes some commands that i want to bind to from the ribbon,but i can't get the binding to work. MainView and EditPanel are usercontrols.
The MainWindow Xaml looks basically like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Command="{Binding ElementName="MainTabControl", Path=SelectedContent.xxxx }" />
> this gives me the MainViewModel, not the MainView
<Ribbon Grid.Row="0" />
<TabControl Name="MainTabControl"
Grid.Row="1"
ItemsSource="{Binding ListOfMainViewModels}">
<TabControl.ContentTemplate>
<DataTemplate>
<view:MainView DataContext="{Binding Data}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
MainView.xaml can be boiled down to:
<Grid>
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0"
Content="{Binding CurrentContentView}" />
<ContentControl Grid.Column="1">
<ContentControl.Content>
<View:QuickEditView DataContext="{Binding CurrentViewViewModel.SelectedItem}" />
</ContentControl.Content>
</ContentControl>
</Grid>
and finally the QuickEditView that has an editor element that exposes the needed commands:
<Grid>
...
<sccontrols:MemoEditControl />
...
</Grid>
There is no problem if i try a binding from the MainView to the editorcontrol, but i can't get it to work from the Ribbon in the MainWindow.
The problem seems to me that i can't get at the MainView in the tabcontrol. the selecteditem as well as the selectedcontent is a MainViewModel, not a MainView and i'm reluctant to keep a reference to the view in the viewmodel
Any suggestions? Thanks in advance.
edit: MainViewModel in broad strokes
public class MainViewModel : INotifyChanged etc
{
public System.Collections.ObjectModel.ObservableCollection<DisplayStyles> AvailableDisplayStyles {...}
public DisplayStyles SelectedDisplayStyle {...}
public SelectableListViewModelBase<DetailViewModel> CurrentViewViewModel{...}
public System.Windows.Controls.Control CurrentContentView{...}
...
}
the MainWindowViewModel holds not much but a list of MainViewModels as well as the selectedMainviewModel + bunch of commands
my current ideas are either to run through the visual tree to find the right element (i suspect the performance will be an issue here), or to push the element up through dependency properties on the encapsulating controls(bit of a nightmare to maintain in the long run i imagine)

Related

Command binding is not propagating for control into a DataTemplate of ItemTemplate

For Command "CommandZoomIn", CanExecute and Execute do not occurs for controls defined into the ListBox ItemTemplate. GraphLcView method "CanExecute" and "Execute" are both called when GraphLcView UserControl is defined directly as child of AnalysisView, but both methods are never called when they are added as Item DataTemplate of a ListBox ItemTemplate.
The button with the command is defined in my top level window into a Ribbon.
Simplified hierarchy:
(Working) Top Level window -> AnalysisView -> GraphLcView
(Not working) Top Level window -> AnalysisView -> ListBox + ItemTemplate -> GraphLcView
The CommandBinding is defined into GraphLcView child control (UserControl.CommandBinding)
There is no MVVM implied into the CommandBindind
Update: I made a working sample to demo the problem but I had a different behavior than explained here. But the fully working sample should probably show something similar to what I have here. Because the bahvior is different, I asked another question at StackOverflow. Code is available here at GitHub
User Control 'GraphLcView' partial code:
<UserControl x:Class="GraphCtrl.GraphView.GraphLcView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
<Grid.CommandBindings>
<CommandBinding Command="{x:Static graphCtrlCommand:CtrlAnalysisCommand.CommandZoomToFitsAll}" CanExecute="CanZoomToFitsAll" Executed="ZoomToFitsAll"/>
<CommandBinding Command="{x:Static graphCtrlCommand:CtrlAnalysisCommand.CommandZoomIn}" CanExecute="CanZoomIn" Executed="ZoomIn"/>
<CommandBinding Command="{x:Static graphCtrlCommand:CtrlAnalysisCommand.CommandZoomOut}" CanExecute="CanZoomOut" Executed="ZoomOut"/>
UserControl AnalysisView partial code (where previous GraphLcView UserControl is used):
<!-- ********************************-->
<!-- ********************************-->
<!-- CommmandBinding works fine here -->
<!-- ********************************-->
<!-- ********************************-->
<graphView1:GraphLcView Grid.Row="1" x:Name="GraphView" Graph="{Binding Graph}"
Visibility="{Binding IsMain, Converter={StaticResource BooleanToVisibilityConverter1}}"
TrackedSignal="{Binding DataContext.LastTrackedSignal, Mode=TwoWay, ElementName=MyControl}"
SourceTrackedSignal ="{Binding Model.EventTrackingSourceGraphToLegend, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=MyControl}"
IsMouseInteractive="{Binding IsMouseInteractive}"
UseFastTooltip="{Binding UseFastTooltip}"
ActiveObjectChanged="OnChildActiveObjectChanged"
>
</graphView1:GraphLcView>
<Grid Name="GridDetails" Grid.Row="1" >
<ListBox Name="ListBoxDetails" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Graph.AdditionalViews}"
Visibility="{Binding IsDetails, Converter={StaticResource BooleanToVisibilityConverter1}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Name="DetailsWrapPanel"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Margin="0,1,0,1"
Width="{Binding DataContext.DetailsWorkspaceDimensionX, ElementName=MyControl, Mode=OneWay}"
Height="{Binding DataContext.DetailsWorkspaceDimensionY, ElementName=MyControl, Mode=OneWay}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Name}"></TextBlock>
<!-- ********************************-->
<!-- ********************************-->
<!-- Binding does not work fine here -->
<!-- ********************************-->
<!-- ********************************-->
<!--ActiveObjectChanged="GraphLcViewDetailOnActiveObjectChanged"-->
<!--SourceTrackedSignal="{Binding DataContext.EventTypeSourceForSignalTrackingToGraph, Mode=TwoWay, ElementName=MyControl}"-->
<graphView1:GraphLcView Grid.Row="1"
AdditionalView="{Binding Path=., Mode=OneWay}"
Graph="{Binding Graph, ElementName=GraphView}"
TrackedSignal="{Binding DataContext.LastTrackedSignal, Mode=TwoWay, ElementName=MyControl}"
SourceTrackedSignal ="{Binding Model.EventTrackingSourceGraphToLegend, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=MyControl}"
IsMouseInteractive="{Binding IsMouseInteractive}"
UseFastTooltip="{Binding UseFastTooltip}"
ActiveObjectChanged="OnChildActiveObjectChanged"
>
</graphView1:GraphLcView>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I'm sorry. After investigation, I realised that the problem came from the LightningChart control which does not keep the focus. I added 2 handlers for "GotFocus" and "LostFocus". Then I realised that everything went fine for the control in the first tab, which is not part of a ListBox itemTemplate. But all others, that are in the second tab, into a ListBox itemTemplate, lost the focus as soon as they got it for no particular reason (at least none that I can understand).
I sent the problem to Arction, the company under LightningChart and they told me they will try to fix it soon.

ViewModels with MVVLight

I am new in MVVM Light. I try to write a project with MVVM Light Framework and I have some questions. I have defined my MainWindow.xaml like this:
MainWindow.xaml:
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Height="30" Width="40" Command="{Binding OpenNews}">News</Button>
<Button Height="30" Width="40" Margin="5,0,0,0" Command="{Binding OpenTeams}">Teams</Button>
</StackPanel>
<ContentPresenter Grid.Row="1" Content="{Binding CurrentViewModel}"/>
</Grid>
MainViewModel:
Contains the two RelayCommands OpenNews and OpenTeams. And also the CurrentViewModel property. This property contains the ViewModel depending on the option which the user clicks (NewsViewModel or TeamsViewModel).I do that like this:
private void ShowOpenTeams()
{
CurrentViewModel = ServiceLocator.Current.GetInstance<TeamsViewModel>(); //MainViewModel.TeamsViewModel;
}
private void ShowOpenNews()
{
CurrentViewModel = ServiceLocator.Current.GetInstance<NewsViewModel>();
}
app.xaml:
I have defined the relation between ViewModel's DataType and the View that should be shown.
<Application.Resources>
<DataTemplate DataType="{x:Type vm:NewsViewModel}">
<view:NewsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:TeamsViewModel}">
<view:TeamView/>
</DataTemplate>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
The question is: why do I need the ViewModelLocator in this case? Does using ViewModelLocator make sense with this approach? I don't see any benefit...
The Views in MVVM Light are created with this relation DataContext="{Binding ViewModelName, Source={StaticResource Locator}}" but I wonder which approach is it usefull with?

Send commands between two usercontrols in WPF

I'm trying to send a command from one UserControl to another. The first one contains a button and the second one contains a custom class that derives from the Border class.
I want when I click the Button in UserControl to execute the Redraw method in CustomBorder in UserControl2.
Here is what I have done so far.
MainWindow.xaml:
<Window x:Class="SendCommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sendCommands="clr-namespace:SendCommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<sendCommands:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<sendCommands:UserControl1 Grid.Row="0"/>
<sendCommands:UserControl2 Grid.Row="1"/>
</Grid>
</Window>
UserControl1:
<UserControl x:Class="SendCommands.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Redraw"
Width="200"
Height="30"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</UserControl>
UserControl2:
<UserControl x:Class="SendCommands.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sendCommands="clr-namespace:SendCommands">
<Grid>
<sendCommands:CustomBorder Background="Black">
</sendCommands:CustomBorder>
</Grid>
</UserControl>
CustomBorder class:
using System.Windows;
using System.Windows.Controls;
namespace SendCommands
{
public class CustomBorder : Border
{
public void Redraw()
{
// Operations to redraw some elements inside the CustomBorder
MessageBox.Show("We did it!");
}
}
}
ViewModel.cs:
namespace SendCommands
{
class ViewModel
{
}
}
Please somebody help me learn this once and for all. I'm new to MVVM concept and I have read a lot but no results. I really need a practical solution to get the concepts right.
What does your Redraw method is really supposed to do? Change border if some property has changed? E.g. an item in a shop was sold out?
In general view should reflect changes in the ViewModel. This happens automatically with bindings. View element, such as button, can communicate with ViewModel with Commands.
Therefore your button would look like this:
<Button Command={Binding ClickCommand} />
In your ViewModel you'll have a
public DelegateCommand ClickCommand {get; private set;}
and
ClickCommand = new DelegateCommand(ExecuteClick);
ExecuteClick would update some properties in the view model, e.g. if you have an online shop, set a SoldOut property of bike object to true.
Your view will in turn bind to properties of Bike and change its appearance if some properties change. Changes like text will happen by themselves, more complicated changes can be achieved with converters (e.g. change bckaground to red in SoldOut is true):
<Resources>
<SoldOutToBckgrConverter x:Key="soldOutToBckgrConverter" />
</Resources>
<Label Content={Binding Path=SelectedItem.Model} Background={Binding Path=SelectedItem.SoldOut, Converter={StaticResource soldOutToBckgrConverter}} />
SoldOutToBckgrConverter implements IValueConverter and converts True to Red.
Note: SelectedItem is again bound to a list, whose source is bound to sth like ObservableCollection on your ViewModel.
So basically you shouldn't call redraw, it should all redraw itself automatically with commands, changes in VM and bindings.
Update to your comment: that's what I tried to show, given that I understood the purpose of your redraw right. In my example with products and red background for sold items this will look like this:
In your VM:
public ObservableCollection<MyProduct> Products {get;set;}
private MyProduct selectedProduct;
public MyProduct SelectedProduct
{
get {return selectedProduct;}
set {
if (selectedProduct != value) {
selectedProducat = value;
RaisePropertyChanged(()=>SelectedProduct;
}
}
}
MyProduct has Model property (real world product model, i.e. brand) and SoldOut.
In your View:
<ListBox SelectedItem="{Binding SelectedProduct, Mode=TwoWay}" ItemsSource="{Binding Products}" >
<ListBox.ItemTemplate>
<Label Content={Binding Path=SelectedItem.Model} Background={Binding Path=SelectedItem.SoldOut, Converter={StaticResource soldOutToBckgrConverter}} />
</ListBox.ItemTemplate>
</ListBox>
Now when you click you button, VM changes SelectedProduct and Binding cahnges background (or border..)
You can use "CallMethodAction" behavior provided by Expression Blend. Add System.Windows.Interactivity.dll to your project and you can bind the method to event. In your case "ReDraw" method must bound to "Click" event. More information on this behavior.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<sendCommands:UserControl1 Grid.Row="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RefreshButtonClick">
<ei:CallMethodAction MethodName="RedrawCustomBorder"
TargetObject="{Binding ElementName=customBorder}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</sendCommands:UserControl1>
<sendCommands:UserControl2 Grid.Row="1" x:Name="customBorder"/>
</Grid>

WPF Determining DataTemplate object a user clicked on in ItemsControl

Assume a simple MVVM Application implemented in WPF:
There is a view with an ItemsControl that uses DataTemplates to resolve a view for various ViewModels in a collection.
What I want to know is, how would I add functionality to allow me to click on a given ViewModel in my ItemsControl to return that element in the container ViewModel?
That is to say, for the given example, I may want be able to click on my WrapPanel and then from my BarnYardViewModel have the particular ViewModel from the ItemSource tag returned. (much like binding the selected item in a ComboBox)
<UserControl x:Class="AGI_ServiceTool.View.DataMonitorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:MyProject.ViewModel">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:MooCowViewModel}">
<StackPanel>
<Image Source="/View/Images/MooCow.png"/>
<label content="This is a Moo Cow"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ChickenViewModel}">
<StackPanel>
<Image Source="/View/Images/Chicken.png"/>
<label content="This is a Chicken"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HorseViewModel}">
<StackPanel>
<Image Source="/View/Images/SarahJessicaParker.png"/>
<label content="This is a Horse"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding MyAnimals}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" ScrollViewer.CanContentScroll="True" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
And a few basic ViewModels:
namespace MyProject.ViewModel
{
class AbstractViewModel : INotifyPropertyChanged { ... }
class MooCowViewModel : AbstractViewModel {}
class ChickenViewModel : AbstractViewModel {}
class HorseViewModel : AbstractViewModel {}
class BarnYardViewModel : AbstractViewModel
{
public BarnYardViewModel()
{
_myAnimals.add(new MooCowViewModel());
_myAnimals.add(new ChickenViewModel());
_myAnimals.add(new HorseViewModel());
}
private ObservableCollection<AbstractViewModel> _myAnimals = new ObservableCollection<AbstractViewModel>();
public ICollectionView MyAnimals{
get { return System.Windows.Data.CollectionViewSource.GetDefaultView(_myAnimals); }
}
}
}
I would use a regular Button styled to have no UI, and would pass the ViewModel to it using the CommandParameter
For example,
<ControlTemplate x:Key="ContentOnlyTemplate" TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ElementName=MyItemsControl, Path=DataContext.MyClickCommand}"
CommandParameter="{Binding }"
Template="{StaticResource ContentOnlyTemplate}"
Content="{Binding }" />
</ItemsPanelTemplate>
</ItemsControl.ItemTemplate>
This will result in each item in your ItemsControl being rendered using a Button, and the Button will execute whatever command you specify and pass the current ViewModel in as the CommandParameter.
In this case, I've specified that the Button.Template should be overwritten to use a ContentPresenter which has no UI, so basically it will be whatever your DataTemplate for each animal is.
There are some other solutions posted at this related WPF question too if you're interested: What is the best way to simulate a Click, with MouseUp & MouseDown events or otherwise?
Any reason why it is a StackPanel yet you want something to know if it is Clicked?
I suggest you to change it to a Button and change the Template for it then hookup the Command property to a property in your ViewModel then said the CommandParamter as the Type

Updating data binding source of a content control in a ResourceDictionary

I want to create a view that has a set of tabs (each, basically a ContentControl) that each have various settings. I then want to have a button that will update all of the data binding rather than updating instantly or having update buttons associated with the controls themselves
So, my control is MEF exported as a ResourceDictionary and is similar to the below
<ResourceDictionary ...>
<DataTemplate DataType="{x:Type vm:AdminViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0">
<TabItem Header="Tests">
<ContentControl Content="{Binding ResultStorage}"/>
</TabItem>
</TabControl>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Update"/>
<Button Content="Cancel"/>
</StackPanel>
</Grid>
</DataTemplate>
</ResourceDictionary>
TestStorage would be like this:
<ResourceDictionary ...>
<DataTemplate DataType="{x:Type data:XmlResultStorage}">
<StackPanel>
<TextBlock Text="Result File Path:"/>
<TextBox Text="{Binding Path=ResultPath, Source={x:Static properties:DataStorage.Default}, UpdateSourceTrigger=Explicit}"/>
<TextBlock Text="Result File Location:"/>
<TextBox Text="{Binding Path=ResultFilename, Source={x:Static properties:DataStorage.Default}, UpdateSourceTrigger=Explicit}"/>
</StackPanel>
</DataTemplate>
What I want to do is when the button to update is pressed to somehow call the update (UpdateSource?) on the ContentControl but I can't see how to do it.
In an ideal world I wouldn't have code-behind and do it all via MVVM or something, but if that's not possible code-behind is fine.
So I have two issues, how do I update data bindings manually via a ResourceDictionary and how do I then cause it to cascade through its child ContentControls?

Categories

Resources