1 Control, two separate binding sources - c#

Good Morning!
I have a WPF application that will display a number of different file types based on command line args it receives. It works fine, but I want to go back and refactor it. I have only been a developer for a few years and would like to master MVVM.
I am using an MVVM design package called Stylet. In my PDF view I am using a Telerik RadPdfViewer control to which Telerik has all this binding stuff built in for you. For example, I am binding the right click context menu with the commands "select all" and "copy" using their pre configured command bindings.
I would like to bind the "Document Source" property TO MY viewmodel so I can pass in the paths of documents I want to load. However, the DataContext of the control is bound to Telerik's CommandDescriptors preventing the binding to my viewmodel.
<telerik:RadPdfViewer x:Name="radPdfViewer" Grid.Row="1"
DataContext="{Binding CommandDescriptors, ElementName=radPdfViewer}"
DocumentSource="{Binding PDFDoc}"
telerik:RadPdfViewerAttachedComponents.RegisterFindDialog="True"
HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch"
telerik:StyleManager.Theme="Office_Black" Grid.ColumnSpan="2">
<telerik:RadContextMenu.ContextMenu>
<telerik:RadContextMenu>
<telerik:RadMenuItem Header="Select All"
Command="{Binding SelectAllCommandDescriptor.Command}" />
<telerik:RadMenuItem Header="Copy"
Command="{Binding CopyCommandDescriptor.Command}" />
</telerik:RadContextMenu>
</telerik:RadContextMenu.ContextMenu>
</telerik:RadPdfViewer>
public class PDFViewModel
{
private string _pdfDoc;
public string PDFDoc
{
get
{
return _pdfDoc;
}
set
{
_pdfDoc = value;
}
}
public PDFViewModel()
{
PDFDoc = #"t:\share\large.pdf";
}
}
I see two choices
I break Telerik's prebuilt command bindings and figure out how to bring the select all and copy functions to my viewmodel.
Stylet has an s:Action function where I can call a method where I can load the document into the RadPdfViewer control using C#. I would need to somehow get control of the gui control in the method of my viewmodel and I am not sure how to do that.
Is there a better way? A little nudge in the right direction would be greatly appreciated.

Jason Tyler's reply got me going in the right direction. Thank you!
So because I am using a ViewModel first pattern, I did not need to specify the DataContext of the user control like I thought...Its already set.
However, his suggestion of binding using the relative source and researching on how to do this (I have never used RelativeSource before..I am kinda new to this stuff) I came across this Stack post
How do I use WPF bindings with RelativeSource?
A Jeff Knight Posted a diagram of how ancestor binding works.
Using that, I was able to figure out the syntax and my document came right up and I can still use the right click context menu items that are bound to Telerik. So now my Xaml looks like this note how the Document source binding has changed.
<telerik:RadPdfViewer x:Name="radPdfViewer" Grid.Row="1"
DataContext="{Binding CommandDescriptors, ElementName=radPdfViewer}"
DocumentSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DataContext.PDFDoc}"
telerik:RadPdfViewerAttachedComponents.RegisterFindDialog="True"
HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch"
telerik:StyleManager.Theme="Office_Black" Grid.ColumnSpan="2">
<telerik:RadContextMenu.ContextMenu>
<telerik:RadContextMenu>
<telerik:RadMenuItem Header="Select All"
Command="{Binding SelectAllCommandDescriptor.Command}" />
<telerik:RadMenuItem Header="Copy"
Command="{Binding CopyCommandDescriptor.Command}" />
</telerik:RadContextMenu>
</telerik:RadContextMenu.ContextMenu>
</telerik:RadPdfViewer>

Related

Click handler of button in datatemplate not working

I have a resource dictionary combining a number of datatemplates. I'm including this resource dictionary as a ResourceDictionary.MergedDictionaries in my Page.Resources. One of my datatemplates is a ListView and while the item source and item click is working correctly, a separate button on the ListViewItem, set in the datatemplate, is not calling my click method. Im unsure about setting this up correctly.
This click method is defined in the code behind class the defines the pages Xaml including the resource dictionary and using my datatemplate for ListViewItems.
Dictionaries
DataTemplates.xaml <- ListView template here with a button click defined in the page cs, i.e. Click="MyPages_ClickMethod"
Pages
MyPage.xaml
MyPage.xaml.cs <- click method defined here, MyPages_ClickMethod()
Here is how I am setting up the button in the datatemplate:
<Button Tag="{Binding id}" Click="MultiShareSelectFileButton_Click" Background="Transparent" Visibility="{Binding multiShareSelected, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Inverted, Mode=OneWay}">
<Image Width="27" Source="ms-appx:///Assets/sharePlusIcon#2x.png" VerticalAlignment="Center" HorizontalAlignment="Center">
</Image>
</Button>
Is it possible to do this without using ICommand?
Something like: Click="{x:Bind Path=pages:ProductPage.MultiShareSelectFileButton_Click}", but this is complaining that MultiShareSelectFileButton_Click should be static
I'll get right to it. Here is the issue,
Your DataTemplate is in a resource dictionary. The resource dictionary is made for styles and converters if I may. Putting the DataTemplate in a resource dictionary is not recommended.
Why isn't it recommended?
The reason is straight, resource dictionaries are used to put global data. For ex: a control style that you might want to be available through out your app or your converters which are being used frequently.
This is because generally you would define the resource dictionary in your app.xaml which runs when your splashscreen appears.
Now if you have a lot of stuff (DataTemplates, Styles, Converters) all defined into resource dictionaries that are merged in <Application.ResourceDictionary> part of app.xaml, it's gonna have a significant impact on your app launch time, which will spoil your user's experience.
What's advised?
It's advised to keep your converters and styles not global unless you need them everywhere. For example: If you have a BoolToVisibilityConverter or a CustomRoundButtonStyle which you use only on one page/userControl out of 4. Then it doesn't make sense to load the style or converter for the other 3 Pages. So you should declare them in <Page.Resources> instead.
Same for your DataTemplate why declare it globally if you want to use it just once. Rather declare it to your <Page.Resources>. Your problem will be solved immediately as your Page will have a code-behind, so your xaml will know where to look for the method. That's where things are going wrong.
But in-case you have a single DataTemplate to be used on all your Views below is your solution:
Your Solution:
In-case you have to use it in a resource dictionary, use {x:Bind} and x:DataType="Models:YourDataContextModel" to bind your DataTemplate to your model. this ways your xaml will know exactly where to look for the method on click.
Below is a sample of it:
<DataTemplate x:Key="HelloTemplate" x:DataType="yourDefinedNameSpace:YourModel">
<Button Click="{x:Bind GoFetchData}"/>
</DataTemplate>
Where YourModel exists in a namespace defined as "yourDefinedNameSpace" in xaml and it contains a method of signature: internal void GoFetchData()
I hope this helps. Feel free to use the comments section if you have any doubts
I found that it was also necessary to specify ClickMode="Press" in Xaml.
<Button Content="" Focusable="True" FontFamily="Segoe UI Symbol" FontSize="16" Background="{StaticResource HeroLightGray}" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"
ClickMode="Press"
Command="{Binding DataContext.CopyMetadataSourceAsync, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" CommandParameter="{Binding .}" />
I do not recall having to do this in the past, but after spending hours troubleshooting, converting RelayCommand to IAsyncCommand, etc. this is the only thing that worked. In fact, I couldn't even get a regular code-behind "Click event" method to fire unless I included that ClickMode="Press"!

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.

Getting a template element content or text from ControlTemplate

I'm working on a Windows Phone 8 C# and SQLite application. I'm really new to Windows Phone applications and usually work with PHP and JS.
There is a LongListSelector, which every item is a Button. Each Button should reference to an ID which is binded from a class of SQLite:
<phone:LongListSelector x:Name="llsRadios" ItemsSource="{Binding Radios}" ItemTemplate="{StaticResource DataTemplate1}"/>
DataTemplate1:
<DataTemplate x:Key="DataTemplate1">
<Grid>
<Button x:Name="btnFoo" Template="{StaticResource ButtonControlTemplate1}" Click="btnFoo_Click" />
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="True" x:Name="ContextMenu" >
<toolkit:MenuItem x:Name="btnEditFoo" Header="edit" Click="btnEditFoo_Click"/>
<toolkit:MenuItem x:Name="btnDeleteFoo" Header="delete" Click="btnDeleteFoo_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</Grid>
</DataTemplate>
ButtonControlTemplate1:
<ControlTemplate x:Key="ButtonControlTemplate1" TargetType="Button">
<Grid>
<TextBlock x:Name="lblName" Text="Name" />
<TextBlock x:Name="lblCountry" Text="Country" />
</Grid>
</ControlTemplate>
I need to, when a user clicks in one of these buttons, on the event Click, get this ID value that represents a row on SQLite to then run a select * from table where ID = ..., for example.
In JavaScript, I would add a attribute data-id and handle the event like:
this.getAttribute('data-id');
// run an AJAX request
My first idea was to bind to Content of each button the ID, so I could run on btnFoo_click:
Button btn = sender as Button;
var ID = sender.Content;
// Do SQLite select.
But this doesn't seems to be the correct way to do it. Also, further on I have to work with the same concept on ConceptMenus (on hold, two options: Edit and Delete would show. These must do actions to the element which the user was selecting.)
Also I thought that I could access the template of the button and find a hidden element with the same idea of binding a value to its Text or Content attribute. But I couldn't find a way to select element from a template, kind of like jQuery's find: $('.parent').find('.element-i-need');
Sticking with this second idea, how could I search of elements by their names on a ControlTemplate of the clicked button/element?
For the first, do you know that you can get hold of the model via the DataContext? That's the most direct way to do it in a click handler:
Button btn = sender as Button;
var viewmodel = btn.DataContext as MyItemModel;
var ID = viewmodel.ID;
// Do SQLite select.
For the second, you can't access template elements directly from outside the control (there is GetTemplateChild, but that is a protected method). The rough equivalent to the JQuery "find" would be to trace down the visual tree, but that's pretty bad form in XAML, and doesn't always work (not everything is in the visual tree, Popups for example). You could however subclass the Button control, and expose a public property that makes use of GetTemplateChild.
Ideally though, your model logic shouldn't be interacting with the UI at all. When possible it is best to use <Button Command="{Binding SomeCommand}" ... /> where "SomeCommand" is an ICommand implementation, rather than <Button Click="CodeHandler" ... />. That can be easier said than done, though, especially in WP Silverlight without the FindAncestor binding ...

Add bindings to button using a decorator or another option?

I am working on adding functionality into our WPF application, the gist of which is if the user Ctl+Shift clicks on a tab, the tab is moved into a new window (the goal of which is dual monitor support).
I have been trying to find a way to make this logic easy to implement on a new element, my first thought was by use of a decorator but I can find no example nor think of a way to achieve this.
Here is a snippet of the xaml that handles this logic:
<TabItem Header="Overview" TabIndex="6" x:Name="overviewTabItem" Style="{StaticResource UDTab_SecondaryTabItem}">
<TabItem.InputBindings>
<MouseBinding Gesture="CTRL+Shift+LeftClick" Command="{Binding ShowPopupCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TabItem}}}"/>
</TabItem.InputBindings>
<OverviewControl:OverviewControl x:Name="overviewControl"/>
</TabItem>
The part that I would like to apply via a decorator (or other method) is the InputBindings, by necessity it would need to be able to merge in with any additional manually specified bindings a control may have.
The command for this binding, in its final state, will exist on a static object so data context shouldn't be an issue.
Cannot see how decorator can help here.
You can create custom MyMouseBinding: MouseBinding and then reuse it with other controls.
<TabItem Header="Overview" TabIndex="6" x:Name="overviewTabItem">
<TabItem.InputBindings>
<local:MyMouseBinding />
<!-- Other inputs -->
</TabItem.InputBindings>
</TabItem>
The second solution is to use Attached Behaviour, it can look like
<TabItem Header="Overview" TabIndex="6" x:Name="overviewTabItem"
local:MoveToSecondMonitorGesture>
<TabItem.InputBindings>
<!-- Other inputs -->
</TabItem.InputBindings>
</TabItem>
So the solution ended up being painfully simple and I'm ashamed I didn't think of it sooner.
I created a new class called PopoutTabItem which inherited from TabItem and then I set up the bindings I needed in the constructor.
Here is the code:
public class PopoutTabItem : TabItem
{
public PopoutTabItem()
{
InputBindings.Add(new MouseBinding
{
Gesture = new MouseGesture{Modifiers = ModifierKeys.Control | ModifierKeys.Shift, MouseAction = MouseAction.LeftClick},
Command = PopoutManager.ShowPopupCommand,
CommandParameter = this
});
}
}
And the Xaml:
<TabControls:PopoutTabItem Header="Overview" TabIndex="6" x:Name="overviewTabItem" Style="{StaticResource UDTab_SecondaryTabItem}">
<OverviewControl:OverviewControl x:Name="overviewControl"/>
</TabControls:PopoutTabItem>

Bind button in DataTemplate to command in the form's ViewModel

My problem is similar to the one described in this question:
WPF MVVM Button Control Binding in DataTemplate
Here is my XAML:
<Window x:Class="MissileSharp.Launcher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MissileSharp Launcher" Height="350" Width="525">
<Grid>
<!-- when I put the button here (outside the list), the binding works -->
<!--<Button Content="test" Command="{Binding Path=FireCommand}" />-->
<ListBox ItemsSource="{Binding CommandSets}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- I need the button here (inside the list), and here the binding does NOT work -->
<Button Content="{Binding}" Command="{Binding Path=FireCommand}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
It's just a ListBox, bound to an ObservableCollection<string> named CommandSets (which is in the ViewModel).
This binding works (it displays a button for each item in the collection).
Now I want to bind the button to a command (FireCommand), which is also in the ViewModel.
Here's the relevant part of the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand FireCommand { get; set; }
public ObservableCollection<string> CommandSets { get; set; }
public MainWindowViewModel()
{
this.FireCommand = new RelayCommand(new Action<object>(this.FireMissile));
}
private void FireMissile(Object obj)
{
System.Windows.MessageBox.Show("fire");
}
}
The binding of this button does NOT work.
From what I've understood from the question I linked above, the binding doesn't work because:
(correct me if I'm wrong)
The button is inside the ListBox, so it only "knows" the binding of the ListBox (the ObservableCollection, in this case), but not the binding of the main window
I'm trying to bind to a command in the main ViewModel of the main window (which the button doesn't "know")
The command itself is definitely correct, because when I put the button outside the ListBox (see the XAML above for an example), the binding works and the command is executed.
Apparently, I "just" need to tell the button to bind to the main ViewModel of the form.
But I'm not able to figure out the right XAML syntax.
I tried several approaches that I found after some googling, but none of them worked for me:
<Button Content="{Binding}" Command="{Binding RelativeSource={RelativeSource Window}, Path=DataContext.FireCommand}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, Source={StaticResource MainWindow}}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Could someone please:
give me the proper XAML to bind the button inside the ListBox to a command in the form's MainViewModel?
point me to a link where this advanced binding stuff is explained in a way that a WPF/MVVM beginner can understand?
I'm feeling like I'm just copying and pasting arcane XAML incantations, and so far I don't have any clue (and can't find any good documentation) how I would figure out by myself in which cases I'd need RelativeSource or StaticResource or whatever instead of a "normal" binding.
It's:
{Binding DataContext.FireCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}
No need to walk up to the root unless you actually change the DataContext along the way, but as the ListBox seems to bind to a property on the main VM this should be enough.
The only thing i recommend reading is the Data Binding Overview, and the Binding class documentation (including its properties).
Also here is a short explanation on how bindings are constructed: A binding consists of a source and a Path relative to that source, by default the source is the current DataContext. Sources that can be set explicitly are: Source, ElementName & RelativeSource. Setting any of those will override the DataContext as source.
So if you use a source like RelativeSource and want to access something in the DataContext on that level the DataContext needs to appear in the Path.
This may be considered unrelated by most, but this search is only 1 of 3 results that you'll find searching for data binding commands to controls inside a data template--as it relates to Xamarin Forms. So, maybe it'll help someone now-a-days.
Like me you may wonder how to bind commands inside a BindableLayout. Credit jesulink2514 for answering this at Xamarin Forums, where it's probably overlooked by many because of all the comments. Here's his solution, but I'm including the link below:
<ContenPage x:Name="MainPage">
<ListView Grid.Row="1"
ItemsSource="{Binding Customers}"
VerticalOptions="Fill"
x:Name="ListviewCustomer">
<ListView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Property}"/>
<Button Command="{Binding BindingContext.ItemCommand, Source={x:Reference MainPage}}"
CommandParameter="{Binding .}">Click me</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
https://forums.xamarin.com/discussion/comment/217355/#Comment_217355

Categories