usercontrol button binding not responding to relaycommand on mainviewmodel - c#

I have a MainWindow bound to its mainViewModel. inside the MainWindow I have a usercontrol defind like this
<vm:StatPanel DockPanel.Dock="Right" DataContext="{Binding Source={StaticResource viewModel}}" Loaded="StatPanel_Loaded" />
inside that usercontrol I have a datagrid with buttons. The goal is when the buttons are clicked to change a datagrid on the MainWindow xaml. this is what my usercontrol datagrid looks like
<Button Content="{Binding Path=sector}" Command="{Binding Path=filterGridCommand}"></Button>
when I run the application I get the following error.
System.Windows.Data Error: 40 : BindingExpression path error: 'filterGridCommand' property not found on 'object' ''mdSectorDetail' (HashCode=42410114)'. BindingExpression:Path=filterGridCommand; DataItem='mdSectorDetail' (HashCode=42410114); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
I am using a command relay that is located in the MainViewModel. My problem is I dont know how to reference that mainViewModel, i have tried several of the suggested solutions like the following
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type }}}"
Please any suggestions will be helpful. Thank you.

You can use snoop to find out what is your DataContext of Button. I think that in your case is wrong DataContext. If you give me all code of UserControl, I will write you a proper data bidning.

i use a empty "marker" interface for such things.
public interface IMyCommandDataContextHelper {}
the control/window which has the datacontext i wanna reach with relative source has to implement the empty interface.
public partial class MainWindow : IMyCommandDataContextHelper
then i can easily write my xaml with relative source
{Binding Path=DataContext.filterGridCommand, RelativeSource={RelativeSource AncestorType={x:Type local:IMyCommandDataContextHelper}}}
ps: Properties should be PascalCase :)
public ICommand FilterGridCommand {get{...}}

Related

How to bind XAML Command to View -or- Update DataGrid item(s) on modification without INotifyPropertyChanged

I have a WPF DataGrid in a Window with associated View(*.xaml.cs) and ViewModel that is successfully executing a bunch of functionality. However, functions that modify items instead of altering the collection do not update until a sort, resize, etc.
I've found a bunch of search results suggesting the solution is to make the item type implement INotifyPropertyChanged and add/subtract event handlers as appropriate for every item in the collection. I tried for a bit using those examples without success, but frankly it doesn't seem like a great option.
The item type is declared elsewhere in the app and is sharing the object instances with other modules, so I would like not to modify that class. It also seems like a poor design to tie the implementation of the DataGrid's ItemsSource to the item type within; the list container is already an ObservableCollection invoking OnPropertyChanged as needed already, so why should that not be sufficient?
I'm able to update via DataGrid.Items.Refresh() - which unfortunately does not seem to have an overload for specific items/properties instead of updating the entire list, but that's a minor issue - but only my View has a reference to the DataGrid itself (per MVVM), whereas the Command binding is in the ViewModel.
I would actually like to put those Command bindings in the View, and I don't understand why convention is to put those in the VM and thereby bypass the View during a UI event. For example, to delete items I can select them and either press Delete (KeyUp handler is in the View, which then passes selected items as a list to the VM) or select Delete in the context menu (Binding is to an ICommand in the VM, which routes to the same function invoked by the View). Why would it not be more desirable to bind both to the View's event handler (or two handlers both in the View)?
I've seen some results that use a RelativeSource for the Command binding to an ancestor of type UserControl...I've tried with type Window to try to bind to the View's method for naught. As of now my best option is to put a delegate event RefreshListItems on the VM and subscribe to it from the view with a function that invokes DataGrid.Items.Refresh().
This is workable, but in the interest of edification I wondered if anyone could tell me how to bind Command properties to the View (a Window) instead of the ViewModel, and/or how to notify the control bound to my ObservableCollection to refresh either specific or all items from the ViewModel without implementing the INotifyPropertyChanged scheme on every list item?
Edit per mm8's suggestion:
I tried your code for the ICommand in my view, but I get this error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=SetService; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
That's with each of these attempts, with and without CommandParameter, bound to an ICommand of type either RelayCommand or DelegateCommand:
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType=Window}}"/>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
.xaml:
<Window>
…
<DataGrid x:Name="TheGrid" ItemsSource="{Binding Source={StaticResource MessageItems}}" KeyUp="MessageList_KeyUp" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="Auto">
…
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType=Window}}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
…
</Window>
.xaml.cs:
public partial class TheGridView : Window
{
TheGridViewModel _viewModel;
public ICommand SetService;
[ImportingConstructor]
public TheGridView(TheGridViewModel vm)
{
DataContext = _viewModel = vm;
vm.RefreshListItems += () => TheGrid.Items.Refresh();
InitializeComponent();
Closing += Window_Closing;
SetService = new RelayCommand(SetSvc);
}
private void SetSvc(object selectedItem)
{
// Doesn't get here
}
}
This is workable, but in the interest of edification I wondered if anyone could tell me how to bind Command properties to the View (a Window) instead of the ViewModel?
There is nothing that stops you from defining ICommand properties in the code-behind of the view and bind to them like this (assuming your view is a Window):
Command="{Binding YourCommandProperty, RelativeSource={RelativeSource AncestorType=Window}}"
Edit:
A ContextMenu resides in its own visual tree but you should be able to bind to the parent window through the Tag property of the DataGrid, something like this:
<DataGrid x:Name="TheGrid" ItemsSource="{Binding Source={StaticResource MessageItems}}" KeyUp="MessageList_KeyUp" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="Auto">
<DataGrid.Tag>
<Binding Path="." RelativeSource="{RelativeSource AncestorType=Window}" />
</DataGrid.Tag>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Set Service" Command="{Binding PlacementTarget.Tag.SetService, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
...and/or how to notify the control bound to my ObservableCollection to refresh either specific or all items from the ViewModel without implementing the INotifyPropertyChanged scheme on every list item?
You can't do this unless you refresh the entire control (for example using DataGrid.Items.Refresh()). That's why you should implement INotifyPropertyChanged.
If you currently bind to some class that is shared across several modules and you don't want to modify this class, you could create a new client-specifc wrapper class that does implement INotifyPropertyChanged and bind to this one instead of binding to the common class, e.g.:
public class Wrapper : INotifyPropertyChanged
{
private readonly SharedModel _model;
public Wrapper(SharedModel model)
{
_model = model;
}
private string _property;
public string MyProperty
{
get { return _property; }
set { _property = value; OnPropertyChanged(); }
}
//...
public event PropertyChangedEventHandler PropertyChanged;
}

Databinding a TabItem to a parent Window

I'm trying to clean up the way I use DataContexts with my UserControls, and currently am running into a problem where I need to databind a UserControl inside of a TabItem to the parent Window's DataContext.
Here is a sketch of what my Window looks like:
As you can see, this Window owns a TabControl that contains TabItems that are dynamically added via the "Tabs" ItemSource. Databinding at this point is working because "Tabs" gets populated with Tab 1.
Tab 1 contains a UserControl that needs access to multiple string properties in the DiagnosticsViewModel, but when I run my application, the Output window indicates that all bindings have failed. For example:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'RelativeSource FindAncestor,
AncestorType='System.Windows.Window', AncestorLevel='1''.
BindingExpression:Path=Property1; DataItem=null; target element is
'Tab1UserControl' (Name=''); target property is 'UCName' (type
'String')
The XAML for the UserControl in Tab 1 looks something like this:
<Grid>
<uc:Tab1UserControl UCName="{Binding Property1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Grid>
where UCName is a String DependencyProperty.
If I am telling WPF that I want to look up the tree and bind to the DataContext for the nearest Window, and my Window's DataContext is set to DiagnosticsViewModel, why isn't it using it for my UserControl's DataContext? I have not set DataContext = this in my UserControl, as I have done improperly many times in the past, with the expectation that my UserControl will be able to inherit the DataContext from its parent.
I would like to see if Snoop can shed light on my problem, but this GUI is being displayed from a MFC application, and Snoop doesn't seem to be able to attach to my WPF dialog.
If you change the source of a binding using RelativeSource, ElementName or the like, the binding will be directly to the element you specify - not its data context. Which means that in your code the user control will try to bind to a property called Property1 on the Diagnostics class itself.
Try using
<Grid>
<uc:Tab1UserControl UCName="{Binding Path=DataContext.Property1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Grid>
in the code for your user control and see if that fixes the problem.
(As an aside, the idea of the user control itself knowing that the window it belongs to will have a datacontext with a given property seems like a code smell to me, especially since the point of user controls is that they're reusable - it'd feel better to have a dependency property on the user control and then bind that to the appropriate property when you use it. This may just be due to me lacking context, though.)

Dependency Property Datacontext

I have a usercontrol, and there is a Datacontext set for it. This usercontrol contains also a Dependency-Property. Now, i want simply bind to this property.
I think the problem has something to do with the wrong datacontext.
The dependency-Property in my usercontrol (called TimePicker) looks like this:
public TimeSpan Time
{
get { return (TimeSpan)GetValue(TimeProperty); }
set
{
SetValue(TimeProperty, value);
OnPropertyChanged();
}
}
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof (TimeSpan), typeof (TimePicker));
I try to use it like this:
<upDownControlDevelopement:TimePicker Grid.Row="1" Time="{Binding Path=TimeValue}" />
When i do this i get the following binding error:
System.Windows.Data Error: 40 : BindingExpression path error: 'TimeValue' property not found on 'object' ''TimePicker' (Name='TimePickerControl')'. BindingExpression:Path=TimeValue; DataItem='TimePicker' (Name='TimePickerControl'); target element is 'TimePicker' (Name='TimePickerControl'); target property is 'Time' (type 'TimeSpan')
Any help would be highly appreciated
Greetings Michael
PS: you can download the code at here
Although this has now been solved there seems to be some, in my opinion, inappropriate use of the DataContext.
When developing a custom reusable control, you should not set DataContext at all. What the DataContext will be, that is for the user of the control to decide, not for the developer. Consider the following common pattern of code:
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
</Grid>
Notice that here, you are using the Grid control. The developer of the control (in this case, the WPF team), didn't touch the DataContext at all - that is up to you. What does it mean for you as a control developer? Your DependencyProperty definition is fine, but you shouldn't touch the DataContext. How will you then bind something inside your control to the DependencyProperty value? A good way is using a template (namespaces omitted):
<MyTimePicker>
<MyTimePicker.Template>
<ControlTemplate TargetType="MyTimePicker">
<!-- Stuff in your control -->
<TextBlock Text="{TemplateBinding Time}" />
<TextBox Text="{Binding Time, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
<MyTimePicker.Template>
</MyTimePicker>
Note that TemplateBinding is always one-way only, so if you need any editing at all, you need to use normal binding (as you can see on the TextBox in the example).
This only means that the TextBlock/Box inside your control will get its Time value from your custom control itself, ignoring any DataContext you might have set.
Then, when you use the control, you do it like this (added to my first example):
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
<MyTimePicker Time="{Binding TimeValue}" />
</Grid>
What just happened here is that the MyTimePicker does not have DataContext set anywhere at all - it gets it from the parent control (the Grid). So the value goes like this: Data-->(binding)-->MyTimePicker.Time-->(template binding)-->TextBlock.Text.
And above all, avoid doing this in the constructor of your custom control:
public MyTimePicker()
{
InitializeComponent();
DataContext = this;
}
This will override any DataContext set in XAML, which will make binding a huge pain (because you'll have to always set Source manually). The previous example would not work, and this wouldn't work either:
<MyTimePicker DataContext="{Binding Data}" Time="{Binding TimeValue}" />
You would think this is OK, but the DataContext will be resolved in the InitializeComponent() call, so the value will be immediately overwritten. So the binding to TimeValue will look for it in the control instead (which will, of course, fail).
Just don't touch the DataContext when developing a control and you'll be fine.
You don't need to override the data context of user control. You can use RelativeSource to point your binding source property i.e. TimeValue to any other source you like. E.g. If you have the source property in your window's class. You could simply point your binding target to the source in window's data context as follows:
{Binding Path=DataContext.TimeValue, RelativeSource={ RelativeSource AncestorType={x:Type Window}}}
Your error states that 'TimeValue' property not found on 'object' 'TimePicker', which means that the WPF Framework is looking at the 'TimePicker' object to resolve the 'TimeValue' property value. You must have somehow set the DataContext of the Window or UserControl that contains the 'TimePicker' object to an instance of the 'TimePicker' object.
Instead, it should be set to an instance of the class that declares the 'TimeValue' property. If you're using a view model, then you should set it to an instance of that:
DataContext = new YourViewModel();
If the 'TimeValue' property is declared in the Window or UserControl then you can set the DataContext to itself (although generally not recommended):
DataContext = this;
Please note that when data binding to the 'Time' property from inside your TimePicker control, you should use a RelativeSource Binding:
<TextBlock Text="{Binding Time, RelativeSource={RelativeSource
AncestorType={x:Type YourLocalPrefix:TimePicker}}}" ... />
Normally we are not setting datacontext directly.If u want to set datacontext create an instance of your usercontrol and set datacontext individually to each one.

Bind ICommand to parent ViewModel

I have two user controls LeftPanel and DeviceList. DeviceList is inside the LeftPanel. I want the context menu in device list to be able to call a command on the parent view model, which is set on a grid that hosts DeviceList. I have tried the following but it does not work.
Here is the ContextMenu within DeviceList
<MenuItem Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:LeftPanel}},
Path=DeviceListViewModel.RemoveDevice}">
Here is the LeftPanel usercontrol
<UserControl x:Class="Tool.LeftPanel" .... >
<Grid DataContext="{Binding DeviceListViewModel}" Grid.Column="1">
<local:DeviceList Grid.Row="1" Margin="0,0,0,10"/>
I had a similar problem, but the ContextMenu does not see the DataContext of the ViewModel or the Parent.
A possible solution is here Access ViewModel / DataConext inside ContextMenu .
the contextmenu is not part of the visualtree, so i think your binding is simply wrong because the datacontext is not what you think of.
use Snoop to check your DataContext and bindings at runtime
i assume that you have to work with PlacementTarget in your bindings

problem with wpf command not executing when button clicked

I have the following XAML in a WPF application. I would like to bind the button to an ICommand in a view model. For some reason, I am not able to see the command from my view.
this is in a user control.
<Grid>
<Grid.DataContext>
<Binding
x:Name="SettingsData"
Path="Data" />
</Grid.DataContext>
.
.
.
<DockPanel Grid.Column="1">
<Button x:Name="SaveButton"
DockPanel.Dock="Top"
Height="25"
HorizontalAlignment="Left"
Margin="70 0 0 0"
Command="{Binding Path=SaveData}"
>Save Changes</Button>
</DockPanel>
</Grid>
Here is my ICommand object -
public ICommand SaveData
{
get
{
if (_saveData == null)
{
_saveData = new RelayCommand(
param => this.saveData(),
param => true
);
}
return _saveData ;
}
}
Does anyone have any idea why I cannot bind to this command?
Thanks for any thoughts....
Looks like you are setting the DataContext of the Grid to the Data property of your ViewModel (or object). If the object that the Data property exposes doesn't provide the SaveData command, you'll have the problem you're describing. Remember the DataContext is inherited from the parent.
If you require that the DataContext is set in that manner, and still require the button to reference the parent DataContext, one option would be to use a RelativeSource to point to an element that has the ViewModel as the DataContext.
In WPF you also have the option of making those commands static and using the {x:Static} markup extension to reach it.
Hope that helps.
EDIT: Here's an example if your <Grid> is contained in a <UserControl>.
<Button Command="{Binding Path=DataContext.SaveData,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}}}" ... />
Also, I don't know what your full XAML looks like, but I suspect that this can be simplified greatly by removing the DataContext on the Grid and Binding Data on the ItemsControl (or whatever you're using to show the list of objects).
Looking at below error, looks like your DataContext on DockPanel is bound to some sort of List:
I see this in the output window - BindingExpression path error:
'SaveData' property not found on 'object' ''List`1'
Please override Source attribute in Binding if the DataContext is not at the top level

Categories