I have two views:
ShellView - DataContext: ShellViewModel
VolumeViewerView - DataContext: VolumeViewerViewModel
ShellView is the root in my program and it contains 3 VolumeViewerView objects (using their ViewModel instances).
Within each context, things run pretty smooth and as expected.
However, in the VolumeViewerView, I have an <Image> whose Cursor I'd like to control from the ShellViewModel.
Here is the code:
ShellView.xaml - MainView is an instance of VolumeViewerViewModel
<Frame Grid.Row="1" Grid.Column="0" Grid.RowSpan="3" Content="{Binding MainView}"/>
ShellViewModel.cs
private Cursor _editorCursor;
public Cursor EditorCursor
{
get { return _editorCursor; }
set
{
_editorCursor = value;
NotifyOfPropertyChange(() => EditorCursor);
}
}
VolumeViewerView.xaml
<Image [OTHER PROPERTIES]
Cursor="{Binding Path=DataContext.EditorCursor,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:ShellView}}}"/>
When I bind the Cursor to a property in VolumeViewerViewModel things work well as expected. But here it isn't connecting.
I've looked for answers here, here, and here. But none of them is working.
I wonder, is this even possible given they are in different files? Does FindAncestor only work within the context of the same xaml file?
If it isn't possible, what is a good alternative? I can create EditorCursor inside VolumeViewerViewModel and have it set every time the EditorCursor in ShellViewModel is set, but that looks a bit ugly. If it is the only way, I may just elect to go there. But I would really love suggestions!
As #Clemens indicated in the comments, the Visual Tree ends with a Frame element. This was the root of my issues. I had put the child view in a Frame which meant FindAncestor was incapable of finding the parent element and its DataContext.
I edited ShellView.xaml, replaced Frame:
<ContentControl Grid.Row="1" Grid.Column="0" Grid.RowSpan="3" Content="{Binding MainView}"/>
This meant that I also had to edit the child view (it originally inherited Page which cannot be the content of a ContentControl). I made it UserControl and the binding worked as expected.
Related
We have a UI layout that creates a list of Expanders for an ItemsControl.
The ItemsControl is named Items and is by convention binding successfully to our view models internal list, where our viewmodel is of type Conductor<SubViewModel>.Collection.OneActive
my SubViewModel is a Screen and looks like this:
public class SubViewModel : Screen, ISubViewModel
{
public bool IsVisible => true;
public string GroupName => "MyGroupName";
public bool HasValidationFailures => true;
protected override void OnViewLoaded(object view)
{
//overridden to breakpoint on to see when the views get hooked up
}
//various properties and other viewModelly things which should be unimportant
}
The ItemsControl currently correctly creates an Expander for each child view model in the Items list, and the expanders content is also correctly finding the right view by the following Xaml:
<ItemsControl x:Name="Items">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="viewModels:SubViewModel">
<Expander Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
cal:View.ApplyConventions="True"
cal:View.Model="{Binding}">
<Expander.Header>
<WrapPanel>
<TextBlock Text="{Binding GroupName}" />
<ContentControl Style="{StaticResource ErrorIndicator}"
Visibility="{Binding HasValidationFailures, Converter={StaticResource BooleanToVisibilityConverter}}" />
</WrapPanel>
</Expander.Header>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is to facilitate having different layouts as configured by the user. For example we currently bind the same Conductor to a different view which uses a TabControl instead, and allows the groups to be shown in separate tabs.
The tab control also correctly binds the view only when the tab is selected.
After a lot of research online I'm fairly certain I want an ElementConvention but I am having trouble working out what I need to do to get the convention to even get called for an Expander. I have followed a few examples I've found online and written this code to attempt a Convention:
ConventionManager.AddElementConvention<Expander>(Expander.IsExpandedProperty, "IsExpanded", "IsExpandedChanged")
.ApplyBinding = (viewModelType, path, property, element, convention) =>
{
return true;
};
However if I breakpoint on this Funcs body, it never gets hit, and the Views all get hooked up on Expander creation which is why I havn't put any logic in to check if the Expander is expanded...
I have also tried to force ApplyConventions by setting it on the expander... this was experimental but shows in my Xaml for completeness.
I'm happy to explore other routes that isn't a Custom Element Convention but I haven't really found much that would make me believe I should be doing it in a different way.
I am quite new to WPF development, and currently I am trying to use the MVVM on my application development. I have read a lot about MVVM navigation and switching views, but I can't find a solution for my current situation. Let's explain what it is:
First of all, I have my main View element, a Dockpanel, with some fixed areas, and a main "dynamic" area where the content should change, depending on actions:
<DockPanel>
<Label Content="Top Fixed element"/>
<StackPanel Orientation="Vertical" Height="auto" Width="150" DockPanel.Dock="Left">
<Label Content="SomeOptions"/>
<!-- some more elements -->
</StackPanel>
<Label DockPanel.Dock="Bottom" Content="Foot"/>
<ContentControl Content="{Binding CurrentMainViewElementViewModel}"/>
</DockPanel>
I have defined some DataTemplates that I would like to load in this ContentControl, here there is one of the Data Templates as example:
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:FileLoaderVM}">
<View:FileLoaderView/>
</DataTemplate>
</Window.Resources>
This FileLoader (View and View Model are implemented, using the RelayCommand and the INotifyPropertyChanged) opens a dialog box after clicking a button, where after selecting a file it is opened and parsed, and show all the found elements inside a ListView with multiple selection(in this case, persons with their data).
What I want to do now is to load another user control in this ContentControl, when I click a button. This button is defined in my view model like this:
public ICommand LoadPersons
{
get { return new RelayCommand(param => this.loadSelectedPersons(), param => (SelectedPersons!=null && SelectedPersons.Any()));}
}
My question comes at this point, how can I modify the content of the ContentControl, loading another User Control instead of the current one directly from my view model (in this "this.loadSelectedPersons()")?
If this is not possible, how should I approach to solve this problem?
Next to this action, I want to show all the previously selected elements and manipulate in different possible ways (inserting in a DB, saving in another file and so on), and I have already for that the appropriate User Control, that I would like to show in my main view element in the ContentControl section, keeping the other elements as they are originally.
lets see if i get you right.
you have a mainviewmodel with a property (CurrentMainViewElementViewModel) bound to the ContentControl. your MainViewmodel set the FileLoaderVM to this Property. now you wanna show a "new/other" Viewmodel when a File is seleted in your FileLoaderVM?
why dont you simply expose a event from your FileLoaderVM and subscribe to this event in your MainViewModel? if you do so your MainViewModel can then set the "new/other" Viewmodel to the ContentControl
To change content of ContentControl you do not load another user control, but change value of CurrentMainViewElementViewModel (to which ContentControl.Content is bound) to a new ViewModel, which will load another UserControl (defined in DataTemplate same way as FileLoaderVM is).
This looks like a job for main ViewModel (where CurrentMainViewElementViewModel is located).
Easiest solution is to provide a method in that ViewModel
public Switch()
{
CurrentMainViewElementViewModel = SomeViewModel;
}
and call this method from FileLoaderVM.
I have a GridView that has several buttons. One of them is defined by the following template:
<DataTemplate x:Name="SubjectItemTemplate">
<Canvas Width="340" Height="170" VerticalAlignment="Top">
<Controls:ThreeImageButton HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,0,0,0"
NormalStateImageSource="{Binding NormalImage}"
HoverStateImageSource="{Binding HoverImage}"
PressedStateImageSource="{Binding PressedImage}" Command="{Binding Path=NavigateToUnitsPage}"
CommandParameter="{Binding}" Canvas.Left="0" Canvas.Top="0">
</Controls:ThreeImageButton>
</Canvas>
</DataTemplate>
Now I have a custom control as you can see, called ThreeImageButton. The button works fine when I use it on its own. But when I have it in the DataTemplate it won't bind properties to the code behind.
Right now, I have
x:Name="MyThreeImageButton"
in the custom button definition. And I connect to the code-behind like this:
<TextBlock Text="{Binding ElementName=MyThreeImageButton, Path=NormalStateImageSource}"/>
(This is just a test to display the text, in the actual code I would assign an image source to another property that is referred to by an element).
Right now, nothing is displayed in the TextBlock. What is the correct binding syntax I'm supposed to use to reach my properties?
Thanks!
Edit: I am setting the variable in the InitializeComponent function and I am using SetValue on the DependencyProperty.
Edit: Let me add the following information to be more clear
Scenario I:
In DataTemplate for GridView:
<UserControl CustomParameter="Literal Text">
In UserControl:
<TextBlock Text="{Binding CustomParameter}">
in UserControl .cs: this.DataContext = this
works!
Scenario II:
In DataTemplate for GridView:
<UserControl CustomParameter="{Binding ValueFromDataItem">
In UserControl:
<TextBlock Text="{Binding CustomParameter}">
in UserControl .cs: this.DataContext = this
nope!
I see,
So setting up a two-way binding to a custom property in a user control can be tricky because a user control cannot bind to a CLR property. Not only that but setting the data context on a user control has an unexpected effect on the binding inside it.
You can solve these problems with a little slight of code. Basically back your CLR properties with dependency properties and set the data context on a child element instead of the root user control.
Take a look at this sample. Let's pretend you have the following MainPage. That MainPage will eventually use our custom user control. So let's set the stage.
Here's the code-behind:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = new /* your view model */
{
Title = Guid.NewGuid().ToString(),
};
}
}
In the code above I am simulating a complex view model with a simple anonymous class. It would be silly for you to implement your own like this, but at the same time it is silly for me to build a simple sample with the complete scaffolding. I bring this up only so it does not confuse you - as it could look like I am suggesting this approach in prod.
Here's the XAML:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<local:MyUserControl Text="{Binding Title}" />
</Grid>
In the XAML above, there is absolutely nothing special. I already have reference to the user control in the local namespace and I simply declare it here.
Okay, now that we have a consumer of the control, it's worth pointing out that in testing developers can mistakenly think that their binding is working because they test with literal values. Literal values bind fine. It's binding from the underlying view model that hick-ups.
Let's say another thing, some developers tend to avoid dependency properties because the require a little more typing. People remember that [kbd]propdp[/kbd] is a handy Visual Studio snippet that stubs out a dependency property for you.
Take a look at this user control. It has two controls, a TextBox and a TextBlock which are there to demonstrate the OneWay and TwoWay functionality of this binding approach. We also implement INotifyPropertyChanged on the user control. For the most part, adding a view model in the case of a user control is overkill because the user control already acts like a view model. It's up to the developer, but it seems dumb to me.
Here's the code behind:
public sealed partial class MyUserControl : UserControl, INotifyPropertyChanged
{
public MyUserControl()
{
this.InitializeComponent();
}
// text property
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValueDp(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyUserControl), null);
// bindable
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value,
[System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In the ode above, I have create a "Text" property and backed it with a dependency property. For a matter of reuse I have also implemented SetValueDp() which could be used again and again if I had more than a single property. Even though this demo has but one, I wanted to include this because the repetitive logic should certainly be abstracted out like this.
Here's the XAML:
<Grid Background="Black" DataContext="{Binding ElementName=userControl}">
<StackPanel>
<TextBox Text="{Binding Text, Mode=TwoWay}"
MinHeight="100" Padding="15" FontWeight="Light" FontSize="50" />
<TextBlock Text="{Binding Text}"
MinHeight="100" Padding="15" FontWeight="Light" FontSize="50" />
</StackPanel>
</Grid>
In the XAML above, I do nothing special insofar as binding. The syntax simply binds to the Text property using the Mode appropriate to the control. Just like you would do normally. However, what's worth noticing is that the DataContext is NOT set on the user control. Instead, it is set on the Grid. As a point of fact, any control in the tree other than the user control could be used like this. Just don't set the data context of the user control.
That is it by the way.
I have tested it to make sure it works. Demonstrating both one and two way binding is pretty handy here. I might even turn this into a blog in case other developers want to find it and don't discover this question. Thanks for your question!
Best of luck!
As the comments alluded to, your DataTemplate is placing the datacontext of the items to whatever object you are adding to your list. This is not the same as the surrounding user control's data context. If you want to reference that datacontext's commands, do the following in the DataTemplate's bindings:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.NormalImage}
What this is saying is to go out and find the user control ancestor and use its datacontext and then look for the NormalImage property. If you run into problems, check your output window for binding errors. It is very helpful in finding binding problems.
We have a WPF application with a standard MVVM pattern, leveraging Cinch (and therefore MefedMVVM) for View -> ViewModel resolution. This works well, and I can bind the relevant controls to properties on the ViewModel.
Within a particular View, we have an Infragistics XamGrid. This grid is bound to an ObservableCollection on the ViewModel, and displays the appropriate rows. However, I then have a specific column on this grid which I am trying to bind a TextBox text value to a property on the parent DataContext, rather than the ObservableCollection. This binding is failing.
We've gone through several options here including:
Using AncestorType to track up the tree and bind to the DataContext of the parent UserControl like so (from the great answer to this question, as well as this one)...
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
Specifying the ElementName and trying to target the top level control directly. Have a look here if you'd like to read about using ElementName.
Using a 'proxy' FrameorkElement defined in the resources for the UserControl to try and 'pass in' the context as required. We define the element as below, then reference as a static resource...
<FrameworkElement x:Key="ProxyContext" DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"></FrameworkElement>
In this case the binding finds the FrameworkElement, but can not access anything beyond that (when specifying a Path).
Having read around, it looks quite likely that this is caused by the Infragistics XamGrid building columns outside of the tree. However, even if this is the case, at least options 2 or 3 should work.
Our last thoughts are that it is related to the V - VM binding, but even using Snoop we've yet to find what the exact issue is. I'm by no means an expert with WPF binding so any pointers would be appreciated.
EDIT: I have found some templating examples from Infragistics here that I will try.
EDIT 2: As pointed out by #Dtex, templates are the way to go. Here is the relevant snippet for use with a XamGrid:
<ig:GroupColumn Key="CurrentDate">
<ig:GroupColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.CurrentDateTest, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</ig:GroupColumn.HeaderTemplate>
<ig:GroupColumn.Columns>
I've left the XML open... you'd simply add the columns you wanted, then close off the relevant tags.
I dont know about XamGrid but that's what i'll do with a standard wpf DataGrid:
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Since the TextBlock and the TextBox specified in the cell templates will be part of the visual tree, you can walk up and find whatever control you need.
Because of things like this, as a general rule of thumb, I try to avoid as much XAML "trickery" as possible and keep the XAML as dumb and simple as possible and do the rest in the ViewModel (or attached properties or IValueConverters etc. if really necessary).
If possible I would give the ViewModel of the current DataContext a reference (i.e. property) to the relevant parent ViewModel
public class ThisViewModel : ViewModelBase
{
TypeOfAncestorViewModel Parent { get; set; }
}
and bind against that directly instead.
<TextBox Text="{Binding Parent}" />
I have this code part:
<TextBlock
Margin="5,3,5,1" Foreground="White"
FontWeight="Bold" FontStyle="Italic" TextAlignment="Center"
Text="{Binding AntennaName}"/>
and in my viewmodel:
private string antennaName;
public string AntennaName
{
get { return antennaName; }
set { antennaName = value; OnPropertyChanged("AntennaName"); }
}
I checked and I can confirm that in my actual code the AntennaName property does change but the textblock does not.
Can anyone please explain why is this happening? I'm pretty new to the mvvm scene.
Try this -
<TextBlock Text="{Binding DataContext.AntennaName,
RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}"/>
The problem somewhere lies in the way you are setting the DataContext for your UserControl. Somehow, textBlock is not inheriting the DataContext from its parent(UserControl). So, explicitly asking for it might work.
Explanation
UI elements by default search for the Binding in its DataContext unless explicitly specified to look into some other place.
Also, in case you haven't set the DataContext for the control, it will inherit DataContext from its parent Control and look for the Binding property in it. In case the binding property is not found on the parent DataContext either, binding fails silently and all you will see is empty string.
You can always look for Binding failures in the output window. If you look in the output window, you will see your property AntennaName over there.
Refer - Data Binding Overview