WPF: Binding to commands in code behind - c#

I have a WPF Microsoft Surface Application and I'm using MVVM-Pattern.
I have some buttons that are created in code behind and I would like to bind commands to them, but I only know how that works in the XAML
like this:
<Custom:SurfaceButton Command="{Binding SaveReservationCommandBinding, Mode=OneWay}"/>
But I cannot do it like this because my buttons do not exist in the XAML, only in the code behind.
So how would a command binding like that works in code behind?

The accepted answer will work great if the Button has access to the Command. However, in MVVM these are usually kept separate (the Button in the View and the Command in the View-Model). In XAML you'd normally use a data binding to hook it up (like the example in the question).
My program gave me an error when my dynamic Button couldn't find the Command (because it was in a totally different namespace). This is how I ended up solving this:
SurfaceButton.SetBinding (Button.CommandProperty, new Binding("SaveReservationCommand"));

Assuming that you have a named your SurfaceButton to "SurfaceButton1" and you have access to an instance of the command, you can use the following code:
SurfaceButton1.Command = SaveReservationCommand;

I took the code from the link posted by Anvaka as template. I use RadMenuItem of Telerik, but surely you can use any other component that expose Command property.
item = new RadMenuItem();
item.Header = "Hide Column";
DependencyProperty commProp = RadMenuItem.CommandProperty;
if (!BindingOperations.IsDataBound(item, commProp)) {
Binding binding = new Binding("HideColumnCommand");
BindingOperations.SetBinding(item, commProp, binding);
}
//this is optional, i found easier to pass the direct ref of the parameter instead of another binding (it would be a binding to ElementName).
item.CommandParameter = headerlCell.Column;
menu.Items.Add(item);
Hope it helps ... and if something is not clear, sorry, it's my first post :)

This works
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}, Path=SaveReservationCommand}"

Related

Access DataContext of Page from MainWindow (with Telerik)

I am relatively new to WPF and I have stumbled across a problem that I just can't seem to find a solution for.
I am sure that there is already a thread concerning a problem like that but in regard of my lacking knowledge it is very likely that I haven't found it or simply did not understand it.
My problem:
I am developing a WPF-application in C#. It's an Outlook-Styled application with a big MainWindow with a huge ViewModel and XAML.
What I was trying to do, is to split up the single codefiles a bit to make it a little bit more modular and compact.
I am using Telerik Controls and tried to outsource the content of single SplitContainers into Pages, which worked fine until now.
Today, a new situation came up which is somehow stupid and wasn't looking too complicated, but somehow I can't get it to work.
Situation:
I have a Treeview in my "MainWindow" and whenever I change the selection in there, I want to change a property on my Page that I have made a binding to.
So, when I click on an item in "TreeView_3" I want to set a property via EventHandler (SelectionChanged_TreeView3) on the DataContext of "Page_X".
If I had to do this on the MainWindow, I would typically do it like that:
UserViewModel uvm = mainGrid.DataContext as UserViewModel;
Then just call whatever property of specific UserViewModel (ViewModel of the MainWindow) I want to access.
I can't do this the same the same way for the page obviously since "mainGrid.DataContext" will always refer to the MainWindow, since this is where the eventhandler is called.
So what I need would be a little explanation on how to access the DataContext from a page with a different ViewModel.
If you need any code in order to explain, let me know.
You need to separate your concerns. In your code behind your should have only code that handles view related stuff. Most often my codebehind is empty.
In your ViewModels you should handle your data related logic. So instead of casting the datacontext in your code behind, handle a click with a Commandin your viewmodel.
Since there is no possibility to bind a command to the SelectedItemChanged of your TreeView you can use an interaction trigger.
<TreeView xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding Path=SomeCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
Ruven it is hard to say without some example code. But it could be that you need to implement INotifyPropertyChanged on the ViewModels?
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
By calling OnPropertyChanged("PropertyName"); in the setter of a viewmodel property the ui will pick up the change.
Also make sure both views are referencing the same object and not copies of the same object.

XAML reference data context

I am using PRISM to auto-wire my Views & ViewModels, however I have encountered a problem I cannot solve.
I am using a calendar control, which enables users to create new appointments via opening new modal window & saving it to calendar.
This window, is styled via a ControlTemplate, where I have the following item:
<telerik:RadComboBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Margin="3"
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.EmployeeList}">
Items Source of this combobox is the ViewModels DataContext.EmployeeList - ObservableCollection<Employee>.
This would work like a charm as long as it would not be a new pop-out window. That way, I believe it is a userControl as well and therefore my regular code does not recognize any EmployeeList.
There might be 2 ways how to solve it (I don't have direct access to the modal window as it is being automatically generated by the control itself - I am using Telerik suite).
1) Make sure that the ItemsSource will dig deeper than the very first UserControl that it finds. Maybe by slightly changing the code, it will be able to do so? (Maybe using something like AncestorLevel...?).
2) Telerik has shown an example of how to achieve that by the following line:
<local:ViewModel x:Key="ViewModel" /> -- define key first
ItemsSource="{Binding Source={StaticResource ViewModel}, Path=EmployeesSource}"...
BUT the issue with my ViewModel is that under constructor I am passing several interfaces like following:
private readonly IEmployeeRepository _employeeRepository;
public EmployeeView_HolidaysViewModel(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
InitializeCollections();
InitializeCommands();
}
and therefore I can't make the above solution to work at all.
Any help with my problem would be highly appreciated. I simply need to get that list to that modal window's combobox.
In the end I managed to solve the problem by creating additional constructor to my class which looks like following:
public EmployeeView_HolidaysViewModel()
{
_employeeRepository = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IEmployeeRepository>();
InitializeCollections();
}
This way I can easily adopt Solution Nr 2 from the OP.

DataTemplate DataType, no class, just xaml

So I'm trying to understand some code we have on a project that is in C#/WPF. I'm pretty new and just learning whatever I can. Looking at one of the .xaml, we have a DataTemplate that lays out where things go for our app. I want to add some events to it, but there is no code behind the .xaml since it is not a class like other .xamls in our project. The DataType of the DataTemplate points to a ViewModel class, but this class does not see my objects in the DataTemplate. Any thoughts? Thanks.
To add rich event-based behavior to elements created through XAML, you need to utilize attached behaviors.
In addition to the attached behaviors John mentioned above, if you are using MVVM, you can utilize the commanding architecture in WPF. Check out ICommand and implementing those on your ViewModel. You will have something like this:
Command="{Binding YourCommandName}"

Using Events/Commands with XamlReader

I am dynamically building my datatemplate using XamlReader.Parse(string). The problem I have is that I can't put any events on any of the controls I create using the XamlReader. After doing some research online I've learned that this is a known limitation of XamlReader.
I don't know a lot about commands in WPF but could I somehow use them to gain the same result? If so how? If not is there any way I can handle an event in my code behind from a control created using Xaml Reader?
Below is an example of the datatemplate I create. I have the MenuItem_Click event handler defined in the the codebehind of the Window that will host this datatemplate.
I get the following error when trying to run it: System.Windows.Markup.XamlParseException was unhandled: Failed to create a 'Click' from the text 'MenuItem_Click'.
DataTemplate result = null;
StringBuilder sb = new StringBuilder();
sb.Append(#"<DataTemplate
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Grid Width=""Auto"" Height=""Auto"">
<TextBlock Text=""Hello"">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem
Header=""World""
Click=""MenuItem_Click""></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
</DataTemplate>");
result = XamlReader.Parse(sb.ToString()) as DataTemplate;
Hoping a late answer might help others:
I found that I needed to bind the events after the parsing, and had to remove the click event from the Xaml string.
In my scenario I applied the resulting DataTemplate to an ItemTemplate, wired up the ItemSource, and then added the handler. This does mean the click event would be the same for all the items, but in my case the header was the information needed and the method was the same.
//Set the datatemplate to the result of the xaml parsing.
myListView.ItemTemplate = (DataTemplate)result;
//Add the itemtemplate first, otherwise there will be a visual child error
myListView.ItemsSource = this.ItemsSource;
//Attach click event.
myListView.AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(MenuItem_Click));
And then the click event needs to get back to the original source, the sender will be the ListView in my case that was using the DataTemplate.
internal void MenuItem_Click(object sender, RoutedEventArgs e){
MenuItem mi = e.OriginalSource as MenuItem;
//At this point you can access the menuitem's header or other information as needed.
}
Take a look at this link. Most of the solutions there will apply with Parse as well. I'm not really a C# dev, so the only one I can really explain is the last one, which is something of an if-all-else-fails option:
First, you add ID's to your XAML instead of Click, etc attributes. Then you can use FindLogicalNode to get at nodes, and then wire up the events yourself.
For example, say you give your MenuItem ID="WorldMenuItem". Then in your code after calling parse, you can do this:
MenuItem worldMenuItem = (MenuItem)LogicalTreeHelper.FindLogicalNode(result, "WorldMenuItem");
worldMenuItem.Click += MenuItem_Click; // whatever your handler is

Silverlight 4: How to find source UI element from contextmenu's menuitem_click?

I have a datagrid and I added silverlight 4 toolkit contextmenu to textbox in datagrid as follows. When users right click on the textbox, contextmenu is being displayed. When users click the menu item with Header "Test", "MenuItem_Click" is getting executed. Now I want to access the textbox from the MenuItem_Click and modify its properties like background etc. Is there anyway to find textbox element(which is contextmenu's parent) from MenuItem_Click event?
It appears to me that I am missing something very simple.
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding AcctId}"
Style="{StaticResource documentTextBoxStyle}"
ToolTipService.ToolTip="Right Click to modify parameters" >
<toolkit:ContextMenuService.ContextMenu >
<toolkit:ContextMenu >
<toolkit:MenuItem Header="Test" Click="MenuItem_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</TextBox>
</DataTemplate>
There's really no need for a workaround, it's as simple as using the databinding:
(sender as MenuItem).DataContext as TextBox
Will give you the TextBox you're after. (Storing stuff in the Tag field is really not something you want to clutter your code with.)
Though I did not find a solution to this, I found couple of workarounds
Traverse the visual tree and findout the textbox
Modify the code in control toolkit sources to expose the internal member 'Owner' as a public Property which contains reference to the owner of the context menu, in my case, the textbox.
I wonder why SL toolkit guys made the owner to be internal not public. Probably their idea is to manage 'ContextMenu' only through 'ContextMenuService' but unfortunately ContextMenuService doesnt give the Owner. Hopefully SL toolkit guys will give us a way to get the owner of the context menu in future releases.
I'm not sure if this works in Silverlight, but I had a similar issue with WPF recently. If you use the ContextMenu's PlacementTarget property, it should return the element that was used to open the ContextMenu.
All I can suggest is giving your MenuItem a Tag with it's parent's TextBlock name like this:
EDIT: Can't figure out how to paste in Xaml, but I'm sure you know how to add this.
Then in your click event you find the TextBlock:
private void MenuItem_TextBlockClick(object sender, RoutedEventArgs e)
{
MenuItem menuItem = (MenuItem)sender;
TextBlock textBlock = this.FindName((string)menuItem.Tag) as TextBlock;
/// do something
}
The issue I found was the parent of the MenuItem is ContextMenu, which is fine. But once you try and get the Parent of the ContextMenu it just crashes.

Categories