I am building a dynamic context menu. To make that happen I have come up with a custom viewmodel that represents my contextmenu, defined like this:
public class ContextMenuVM {
public object ContextItem { get; set; }
public ObservableCollection<ICommand> Items { get; private set; }
}
The property Items holds the effective commands to show in the contextmenu and the property ContextItem holds the context-item that these commands are to be executed on.
Further, I have a singleton class which holds all my various commands and their implementation. So in the end there will be a method that will be called UpdateContextMenu on the viewmodel that owns the contextmenu that does something like this:
ContextMenu.Items.Clear();
ContextMenu.Items.Add(SingletonClass.Instance.CommandA);
if (condition)
ContextMenu.Items.Add(SingletonClass.Instance.CommandB);
The ContextMenu iteself in XAML is defined like this:
<ContextMenu ItemsSource="{Binding Path=ContextMenu.Items}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<ContentControl>
<MenuItem Command="{Binding}" CommandParameter="what-goes-here?" />
</ContentControl>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
What I fail to achieve is getting the CommandParameter binding to work. How am I supposed to bind to the ContextItem property of the ContextMenuVM instance?
Without a good Minimal, Complete, and Verifiable code example that clearly shows what you're doing, it's impossible to know for sure. But based on the information you've provided so far, it seems like you are looking for the RelativeSource binding source. E.g.:
<ContextMenu ItemsSource="{Binding Path=ContextMenu.Items}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<ContentControl>
<MenuItem Command="{Binding}"
CommandParameter="{Binding ContextMenu.ContextItem
RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContentControl>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
I had to make a guess as to the binding path, based on your ItemsSource binding, because your original code example isn't complete. The basic idea is to bind to a path that is relative to the specified source, so I've assumed that the ContextMenu refers to a property that returns the ContextMenuVM object you are using.
If this doesn't address your question, please improve it by providing a good MCVE.
Related
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;
}
I've added a MenuFlyout to a button in ItemsControl.ItemTemplate. Also I was able to bind current item as CommandParameter.
Now I want to bind Command to a MenuFlyoutItem.
In codebehind :
LayoutRoot.DataContext = this;
So if i bind to LayoutRoot I will actually bind to my current UserControl. But the following binding is not working:
Command="{Binding ActivateProfileCommand, ElementName=LayoutRoot}"
It gives me not errors in Output but it's not working.
Here's the full example:
<controls:HeaderDecorator x:Uid="AccountsHeader" Text="Accounts" x:Name="LayoutRoot" Name="LayoutRoot">
<controls:HeaderDecorator.Resources>
<MenuFlyout x:Key="AccountMenuFlyout">
<MenuFlyoutItem Text="Activate" Name="Activate"
Command="{Binding ActivateProfileCommand, ElementName=LayoutRoot}"
CommandParameter="{Binding}" />
</MenuFlyout>
</controls:HeaderDecorator.Resources>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Settings.Profiles}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<HyperlinkButton Content="{Binding}" FlyoutBase.AttachedFlyout="{StaticResource AccountMenuFlyout}" >
<i:Interaction.Behaviors>
<ic:ShowFlyoutBehavior />
</i:Interaction.Behaviors>
</HyperlinkButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</controls:HeaderDecorator>
Seems the problem is i'm trying to use shared object in Resources. Can I do it? And why not?
The issue you are seeing here is the MenuFlyoutItem is no longer in the datacontext you perhaps think it is. I'll try and explain this as best I can as a few I know who work with xaml have come across this and hit their heads off walls for days about it. It's also known to not show errors in your particular scenario; further increasing confusion.
In a nutshell. When the MenuFlyout is added inside the ItemTemplate of each item in your collection, it does not have access to the datacontext you perhaps think it does. In this case, the datacontext that the control now resides is actually the individual item within the collection it is sitting in.
There is however a solution to this. I have something similar to yourself. An ItemsControl which has it's ItemsTemplate defined that includes a UIElement who's FlyoutBase AP references a MenuFlyout defined in a resource dictionary.
The xaml is pretty much the same except I don't need the ElementName in the binding.
However, I have now turned my attention to the type that the collection holds. I have code that looks something like this.
public class AnItemToList
{
public AnItemToList(Action commandDel)
{
TestCommand = new RelayCommand(commandDel);
}
public string Name { get; set; }
public RelayCommand TestCommand { get; set; }
}
Note that the command is being defined in the item itself and that I'm passing the method that the command will execute via the constructor.
All I have to do for the command binding on the MenuFlyoutItem is
<MenuFlyoutItem Text="Activate"
Name="Activate"
Command="{Binding TestCommand}"/>
I don't have a command param set here as I just quickly put together a basic template Windows Phone app and the pre-packed ICommand implementation doesn't have a delegate set to take the param.
If you now stick a break point in the method the command is calling, you'll see it will be called from any of the MenuFlyoutItem's bound to the command that references it.
Bare in mind that this isn't the only way of solving this problem; but it is one I use myself on occasion. For example, in WPF XAML you can make use of RelativeSource to go looking for the command on a parent control's datacontext.
Hope this helps.
Here's a general "Pair" object:
public class Pair : DependencyObject
{
public static readonly DependencyProperty FirstProperty = DependencyProperty.Register("First",
typeof(object), typeof(Pair), new PropertyMetadata(null));
public static readonly DependencyProperty SecondProperty = DependencyProperty.Register("Second",
typeof(object), typeof(Pair), new PropertyMetadata(null));
public object First
{
get { return GetValue(FirstProperty); }
set { SetValue(FirstProperty, value); }
}
public object Second
{
get { return GetValue(SecondProperty); }
set { SetValue(SecondProperty, value); }
}
}
In ItemTemplate i put something like this:
<DataTemplate>
<Grid>
<Grid.Resources>
<viewModel:Pair x:Key="Tuple" First="{Binding DataContext, ElementName=LayoutRoot}"
Second="{Binding}" />
</Grid.Resources>
<HyperlinkButton Content="{Binding Second.ProfileName}"
DataContext="{StaticResource Tuple}"
FlyoutBase.AttachedFlyout="{StaticResource AccountMenuFlyout}"
</HyperlinkButton>
</Grid>
</DataTemplate>
Now I can easily reference Tuple elements from my Resource like this:
<MenuFlyoutItem Text="Activate" Name="Activate"
Command="{Binding First.ActivateProfileCommand}"
CommandParameter="{Binding Second}" />
So I'm developing a Windows Phone 8 app with the Caliburn.Micro framework. I'm trying to create a grid where I, at runtime add/remove elements such as TextBlock's at runtime. I've tried a few things to bind my code to the x:Name but nothing has worked so far.
So one of the things i tried was having a placeholder grid in my xaml aka View:
<Grid x:Name="ContentPanel" Margin="0,97,0,0" Grid.RowSpan="2">
</Grid>
And then i my ViewModel i use the following to bind my ContentPanel Grid:
private Grid contentPanel;
public Grid ContentPanel
{
get
{
return contentPanel;
}
set
{
contentPanel = value;
NotifyOfPropertyChange(() => ContentPanel);
}
}
I then created a TextBlock to add to the grid:
TextBlock txt1 = new TextBlock();
txt1.Text = "2005 Products Shipped";
txt1.FontSize = 20;
txt1.FontWeight = FontWeights.Bold;
Grid.SetRow(txt1, 1);
And finally i added the TextBlock to my Grid:
ContentPanel.Children.Add(txt1);
When i run this code ContentPanel turn out to be equals null, why is that? Shouldn't Caliburn auto bind ContentPanel x:Name="ContentPanel" with the property ContentPanel?
I would appreciate your help in this matter.
My core problem, that i need solved is this:
I got a login page in my app where i show some pictures and text loaded from a server. As you can see below this is done with Image and a TextBlock When that server is offline or the wi-fi simply aren't enabled i want to replace this picture+text with a static image. Aka i want to remove the TextBlock from the StackPanel.
The part where i load and show the stuff form my server works great and looks like this in my xaml:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
So when the server is offline/wifi disabled i want to replace that with. so that the TextBlock is no longer there:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
Is this even possible? If not what would the best semi-solution be?
EDIT 1: I've managed to setup the flow following the instructions from the accepted answer. But my BooleanToVisibilityConverter is not called, though my NotifyOfPropertyChange(() => IsConnectionAvailable); is getting called.
My Property:
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
How i change the bool: This code is called in my constructor for my ViewModel(just as a test to see if it was working):
IsConnectionAvailable = false;
TextBlock (without trigger code cause its the same as previous):
<TextBlock Text="{Binding Title}" Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BoolToVisibility}}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
It's like the Binding IsConnectionAvailable isn't working because i can change the name IsConnectionAvailable in my Xaml to anything and my NotifyOfPropertyChange(() => IsConnectionAvailable); will still be called.
Any ideas?
I can't even do a normal bind Visibility="{Binding Path=IsVisibil,Mode=TwoWay} to a public Visibility IsVisibil property. I've done this in other classes, but even this won't work??
EDIT 2: The problem that course the binding not to work, seems to lie somewhere in this code:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" Visibility="{Binding Path=IsVisibil,Mode=TwoWay}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
Solution to EDIT 1 and 2: I created an x:Name"Root" at the top of my xaml structure. Then changed the binding to:
ElementName=Root, Path=DataContext.IsVisibil
This is needed because the binding to visibility that I'm trying to set is inside another DataContxt.
This isn't the correct way to use CM, there are a number of areas where you are confusing the model and viewmodel and the binding functionality in CM.
What you are doing currently
You are attempting to have the CM framework look for a property called ContentPanel on your ViewModel and automatically figure out what properties on Grid to bind it to...
This won't work because of a few reasons:
I don't think there is a convention for Grid in CM - it's not really bindable in an obvious way (it's a layout container)
Grid is not a data enabled control - it doesn't know how to consume a collection and display dynamic rows out the box (it's a layout container)
What you are doing doesn't really make any sense (you have an instance of a grid in your UserControl and you have also instantiated a grid in your ViewModel - these are two separate instances of a control - you can't 'bind' them together - that's not how it all works)
CM and Bindings
When you using element name bindings e.g. x:Name with CM, it attempts to find a property on the ViewModel which matches the element name. At this point, depending on the conventions setup for the source control in question, CM will attempt to automagically wire up all the bits and pieces.
There are default conventions contained in ConventionManager which determine which properties to bind when you use element name bindings - e.g. for TextBlock, the Text property on the TextBlock is bound to the target property on the ViewModel.
http://caliburnmicro.codeplex.com/SourceControl/latest#src/Caliburn.Micro.Platform/ConventionManager.cs - look at the class constructor on ConventionManager to see the out of the box conventions - there isn't one for Grid
Once a target property is found, CM will bind it up.
(As an aside: it's worth noting that if the control type is a ContentControl CM will do some composition magic so you can have viewmodels that contain other viewmodels and have a composition all bound up at runtime - great for screens which have multiple sub-windows etc)
The problem you have is that there is no convention setup for Grid out of the box - this is most likely because a Grid in SL/WPF is primarily used for layout, and is not really a 'data container' or data aware in any way (apart from the few dependency properties you can bind to) - i.e. I don't think it's possible to bind to a grid and get a dynamic number of columns/rows without some customisation to the control, hence the omission of any conventions
(think about it - if you are binding a grid to a collection, what should the grid do... add rows or columns? It can't really be supported in a sensible way)
Now bringing it back to SL/WPF for a sec:
Usually if you want a variable list of items you will need to bind to the ItemsSource property of a control which inherits from ItemsControl (or ItemsControl itself).
Many controls do this: if they need to display a dynamic number of items they will usually inherit from ItemsControl.
How does this tie in with CM?
Caliburn Micro knows how to bind up ItemsControl out of the box. This means you can have a property on your ViewModel containing a collection of items and after binding you get a dynamic view of these at runtime
For example - a CM bound ItemsControl might look like this:
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeTextualProperty}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now you just need a collection of objects to bind this to - each item in the collection becomes a new item in the control with its DataContext pointing to the bound item. I've made the assumption that you would want each item to be a ViewModel which contained the property SomeTextualProperty - I've defined that here...
// Provides a viewmodel for a textual item
public class TextItemViewModel
{
public string SomeTextualProperty { get; set;}
}
The VM that should contain the list of items would need to have a collection to bind against.
(Note: Since you are adding items to it at runtime you need to tell the UI when the collection changes - ObservableCollection gives you this for free as it implements collection changed notification events)
// This is the viewmodel that contains the list of text items
public class ScreenViewModel
{
public ObservableCollection<TextItemViewModel> TextItems { get; set; }
}
What else I would consider the incorrect approach
Your ViewModels shouldn't know about your View implementation i.e. they shouldn't reference any type of controls unless absolutely necessary (I can't think of a time when I had to put a control in a VM). ViewModels should model the view - but they shouldn't really need to know any specifics about what that view contains - this way they are more easily testable and they are easily reused
If you follow the above approach, you can get away with providing an application which re-uses the set of viewmodels, but provides different views for each. You can try this by replacing ItemsControl with another type of control in the view (as long as it's data aware such as a datagrid) and the VM will still work - the VM is view agnostic.
Your use of Grid in your VM is not ideal because Grid is a visual control, it is not data. Remember that the visuals are your View and the ViewModel should just contain data and events which notify the view of things happening
If I was doing this - the code would look more like the code I posted above.
To sum up
Model the information you wanted to show in a ViewModel (TextItemViewModel)
Add a collection of these objects to the main ViewModel (ScreenViewModel) using a change aware collection such as ObservableCollection
Add/remove items from the collection using the standard add/remove
Bind the ItemsControl in the view using x:Name bindings to the collection on your ScreenViewModel
Adding/removing items in the VM will fire property changed notifications. ItemsControl will watch for these events and update itself accordingly
Addendum
You could get away with just using an ObservableCollection<string> instead of a TextBlockViewModel but it's not clear if you want to add more properties to the items you are binding to the grid (such as IsHeading property for headings which you could then make bold/italic in the view)
If you want to just use strings just modify the DataTemplate to bind directly to the DataContext rather than a property on the DataContext
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
**<TextBlock Text="{Binding}"/> <!-- Bind direct -->**
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit:
Ok in your case it's quite simple - your ViewModel should simply model the state of the server:
public class LoginPageViewModel
{
public bool IsConnectionAvailable { get; set; } // or whatever your variable should be called
}
Then bind the visibility of the textblock to this using a converter:
<TextBlock Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
You will need to declare the static resource for the converter somewhere (in the control itself or your main resources dictionary for example)
It looks like there is a converter already defined in System.Windows.Controls somewhere, but in case you can't find it the implementation is pretty simple (you could probably do this a bit better to guard against invalid input but for brevity I've kept it tiny):
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture)
{
throw new NotImplementedException();
}
}
You may also want to change the state from available/unavailable during the views lifecycle, so in that case you probably want to use the property changed events built in to PropertyChangedBase (which Screen also inherits) to let the view know when the property changes
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
Addendum 2
I prefer the terse CM syntax instead of being explicit when binding action messages - so your XAML would change from:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
To
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128" cal:Message.Attach="[Tap] = [LoadAdvertisement($dataContext.Link)]"></Image>
(actually that might not be right with the $dataContext.Link part ... but then again it might be... see here: http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Actions&referringTitle=Documentation)
How can I unset the binding applied to an object so that I can apply another binding to it from a different location?
Suppose I have two data templates binded to the same object reference.
Data Template #1 is the default template to be loaded. I try to bind a button command to a Function1 from my DataContext class:
<Button Content="Button 1" CommandParameter="{Binding }" Command="{Binding DataContext.Function1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
This actually works and the function gets binded. However, when I try to load Data Template # 2 to the same object (while trying to bind another button command to a different function (Function2) from my DataContext class):
<Button Content="Button 2" CommandParameter="{Binding }" Command="{Binding DataContext.Function2, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
It doesn't work and the first binding is still the one executed. Is there a workaround to this?
EDIT (for better problem context):
I defined my templates in my Window.Resources:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<local:View1 />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<local:View2 />
</DataTemplate>
</Window.Resources>
The View1.xaml and the View2.xaml contain the button definitions that I described above (I want them to command the control of my process flow).
ViewModel1 and ViewModel2 are my ViewModels that implement the interface IPageViewModel which is the type of my variable CurrentPageViewModel.
In my XAML, I binded ContentControl to the variable CurrentPageViewModel:
<ContentControl Content="{Binding CurrentPageViewModel}" HorizontalAlignment="Center"/>
In my .CS, I have a list defined as List<IPageViewModel> PageViewModels, which I use to contain the instances of my two View Models:
PageViewModels.Add(new ViewModel1());
PageViewModels.Add(new ViewModel2());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
When I try to change my CurrentPageViewModel to the other view model, this is when I want the new binding to work. Unfortunately, it doesn't. Am I doing things the right way?
If for some reason you are unable to use just two different DataTemplates, usually because the datatemplates are very large or complex, i suggest using ContentControl and DataTemplateSelector.
In your DataTemplates place another ContentControl, create 2 DataTemplates just containing your button, one with Function1 one with Function2. Create a DataTemplateSelector and set it on the initial ContentControl. The DataTemplateSelector now just need to select the proper template depending on a decision, for example the type of the item or a property on the item etc.
If you still want to unset binding you can do it from code like:
BindingOperations.ClearBinding(txtName, TextBox.TextProperty)
But TemplateSelector approach will be more efficient.
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