I'm trying to use Caliburn Micro and MVVM for a menu. I'm currently using the Ribbon controls located in System.Windows.Controls.Ribbon. Binding the click event using x:Name works ok for regular buttons in a tab, but the menu items in RibbonApplicationMenu does not work. Is that a bug or do I have to do some extra work in the bootstrapper class to make sure that CM finds the RibbonApplicationMenuItems? I'm on CM v2.0.1.
Some example here:
XAML:
<Ribbon x:Name="Ribbon" >
<Ribbon.ApplicationMenu>
<RibbonApplicationMenu KeyTip="F">
<RibbonApplicationMenuItem Header="{Binding NewText}" x:Name="AppNew" />
</RibbonApplicationMenu>
</Ribbon.ApplicationMenu>
<RibbonTab Header="{Binding OverviewTabText}" >
<RibbonGroup x:Name="MainGroupOverview" Header="{Binding MainGroupText}">
<RibbonButton Label="{Binding NewText}" x:Name="AppNewOverview" />
</RibbonGroup>
</RibbonTab>
</Ribbon>
And in ViewModel class:
public void AppNew()
{
//this will not be called
New();
}
public void AppNewOverview()
{
//this works just fine
New();
}
Still digging on this but for the name attributed doesn't seem to work for anything, actually mocked up a sample on my end. cm:Message.Attach="[Event Click]=[Action NewApp()]" works. Actually strange and you might want to drop by the GitHub repo and fill out if this might actually be an issue. –
Related
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>
I use Windows Ribbon for WPF with Caliburn Micro. Is it possible to move RibbonTabs into different views with ViewModels so views can be more readable?
I have MainToolbarViewModel class bound with a MainToolbarView.
MainToolbarViewModel:
public TabViewModel Tab { get; set; }
public MainToolbarViewModel()
{
this.Tab = new TabViewModel();
}
MainToolbarView:
<ribbon:Ribbon HorizontalAlignment="Stretch" VerticalAlignment="Top" SelectedIndex="0">
<Ribbon.HelpPaneContent>
<RibbonButton SmallImageSource="../Resources/WindowsIcons/help.png"></RibbonButton>
</Ribbon.HelpPaneContent>
<ContentControl x:Name="Tab"></ContentControl>
</ribbon:Ribbon>
I want the ContentControl to work as a RibbonTab. This approach works well with standard controls, but with ribbon it just simply doesn't show anything.
TabViewModel:
public class TabViewModel : Screen
{
public TabViewModel()
{
DisplayName = "New Tab";
}
}
TabView:
<UserControl ......>
<RibbonTab IsSelected="True" Header="{Binding DisplayName}">
<RibbonGroup Header="Types">
<RibbonButton Content="Test"></RibbonButton>
</RibbonGroup>
</RibbonTab>
</UserControl>
I read about making ViewModels for RibbonTab, RibbonGroup, RibbonButton but this just seems crazy, because I would need to create ViewModel for every control.
Additionally most of the answers I read were at least 1 year old. Anything changed since? What is the easiest way to move RibbonTabs to a different viewmodel?
UPDATE:
<ContentControl x:Name="Tab"></ContentControl>
Renders RibbonTab's content inside its header, so basically I have RibbonGroup
inside RibbonTab's header.
I think it's because Ribbon treats <ContentControl> as a header of some sort rather than RibbonTab.
I have the following XAML code:
<ListBox x:Name="ListBox1"
ItemsSource="{Binding UserBase}"
SelectedItem="{Binding User}"
SelectionMode="Single"
AllowDrop="True"
myOwnDragDrop:DragDropSource="{Binding}"
myOwnDragDrop:DragDropTarget="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" AllowDrop="True" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox x:Name="ListBox2"
ItemsSource="{Binding UserBase}"
SelectedItem="{Binding User}"
SelectionMode="Single"
myOwnDragDrop:Source="{Binding}"
myOwnDragDrop:Target="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" AllowDrop="True" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So this actually works in my MVVM implementation (at least the dragging and dropping part). The problem is that in the interface of both of the dependent properties only gives me:
public void Drop(object data)
Where data is actually the entire view object in which ListBox1 resides. In theory there is no problem, but I was wondering how do I get the correct user that was dropped upon without making any changes to the interface or dependent property implementation?
Note the dependent property seems to be handling the event private static void Drop(object sender, DragEventArgs e), but I can't access the e.
In case this is totally impossible, is it possible to retrieve the user that was dropped upon from the DragEventArgs?
Thank you for everything!
First of all: Have you looked at GongSolutions WPF DragDrop library?
It's free, and available on NuGet, and makes implementing drag/drop in WPF a lot easier, but with still retaining complete control over the process.
In particular, the Drop() method exposes an IDropInfo object, which gives you access to all sorts of information about the drag/drop process (including the source and target UIElements).
I spent quite a bit of time trying to implement drag and drop from scratch a while ago, but found this library far easier to implement, and it allowed me to add all my drag/drop functionality to my ViewModel rather than my codebehind.
There's some good get-you-started documentation on the GitHub pages.
Secondly, with regards to how do you obtain the user the drag was dropped onto (without using the above library):
When I implemented drag/drop between listboxes, I found I needed to enable and handle AllowDrop on each ListItem. You might want to try creating a custom ItemPanelTemplate for your listbox, and enabling AllowDrop on the panel. That way, the 'object data' you will receive should the the ListItemPanel, from which you can access the DataContext to obtain the User.
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>
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