The default DataTemplate in a wpf application displays the result of the .ToString() method. I'm developing an application where the default DataTemplate should display nothing.
I've tried:
<Grid.Resources>
<DataTemplate DataType="{x:Type System:Object}">
<Grid></Grid>
</DataTemplate>
</Grid.Resources>
But this doesn't work. Does anyone knows if this is possible without specifiing a specific DataTemplate for every class type in the application?
If you are using the MVVM pattern and have an abstract class which all your ViewModel classes derive from, you can use that class instead of System.Object:
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:VMBase}">
</DataTemplate>
</Grid.Resources>
I know of no way to do this. As per Joe's comment below, WPF specifically disallows specifying a DataTemplate for type Object.
Depending on your exact requirements, it may be easier to search for a DataTemplate that matches the specific type. If you find one, use it. Otherwise, display nothing. For example:
<ContentControl Content="{Binding YourContent}" ContentTemplateSelector="{StaticResource MyContentTemplateSelector}"/>
And in your selector (pseudo-code, obviously):
var dataTemplateKey = new DataTemplateKey() { DataType = theType; };
var dataTemplate = yourControl.FindResource(dataTemplateKey);
if (dataTemplate != null)
{
return dataTemplate;
}
return NulloDataTemplate;
I used Nullable, worked for my situation.
<DataTemplate DataType="{x:Type sys:Nullable}">
<!-- Content -->
</DataTemplate>
I'm not sure about replacing the default DataTemplate, but you can use a ValueConverter to pass display ToString in the case of certain types and an empty string otherwise. Here's some code (note that the typeb textblock doesnt have the converter on it to show what it looks like normally):
.xaml:
<Window x:Class="EmptyTemplate.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:EmptyTemplate"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<loc:AType x:Key="atype"/>
<loc:BType x:Key="btype"/>
<loc:TypeConverter x:Key="TypeConverter"/>
</Window.Resources>
<StackPanel>
<Button Content="{Binding Source={StaticResource atype}, Converter={StaticResource TypeConverter}}"/>
<Button Content="{Binding Source={StaticResource btype}, Converter={StaticResource TypeConverter}}"/>
<TextBlock Text="{Binding Source={StaticResource atype}, Converter={StaticResource TypeConverter}}"/>
<TextBlock Text="{Binding Source={StaticResource btype}}"/>
</StackPanel>
</Window>
.xaml.cs:
namespace EmptyTemplate
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public class AType { }
public class BType { }
public class TypeConverter : IValueConverter
{
public DataTemplate DefaultTemplate { get; set; }
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.GetType() == typeof(AType))
{
return value.ToString();
}
return DefaultTemplate;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
Here a working example about how to do this using a selector (the best way IMO):
public class EmptyDefaultDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item != null)
{
var dataTemplateKey = new DataTemplateKey(item.GetType());
var dataTemplate = ((FrameworkElement) container).TryFindResource(dataTemplateKey);
if (dataTemplate != null)
return (DataTemplate) dataTemplate;
}
return new DataTemplate(); //null does not work
}
}
I discovered something accidentally. I was using a custom dependency property to set the Datacontext on a usercontrol that had a contentcontrol with Datatemplates based on types(entities in my case). Since I had several different kinds of entities my custom dependency property was
` typeof(object)
This was the device I used to bind to the datacontext of the ContentControl.
public object MySelectedItem
{
get { return (object)GetValue(Property1Property); }
set { SetValue(Property1Property, value); }
}
public static readonly DependencyProperty Property1Property
= DependencyProperty.Register(
"MySelectedItem",
typeof(object),
typeof(PromotionsMenu),
new PropertyMetadata(false)
);
Used like this:
MySelectedItem = SomeEntity;
I discovered I could also use it like this:
MySelectedItem = "some text";
And the contextcontrol would print some text as its context.
MySelectedItem = "";
works for a totally blank context.
`
Related
I am having a few issues with using Caliburn Micro's Conductor<>.Collection.OneActive with MahApps.Metro HamburgerMenu. From a few samples, but none of them address my scenario.
All of my code is available in this Github repository.
I want to show a set of panes inside a HamburgerMenu. Each pane has a title and a display name:
public interface IPane : IHaveDisplayName, IActivate, IDeactivate
{
PackIconModernKind Icon { get; }
}
In my case, IPane is implemented using PaneViewModel:
public class PaneViewModel : Screen, IPane
{
public PaneViewModel(string displayName, PackIconModernKind icon)
{
this.Icon = icon;
this.DisplayName = displayName;
}
public PackIconModernKind Icon { get; }
}
This has the following view:
<UserControl x:Class="CaliburnMetroHamburgerMenu.Views.PaneView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Padding="12"
Background="Pink">
<StackPanel Orientation="Vertical">
<TextBlock Text="Non-bound text" />
<TextBlock x:Name="DisplayName" FontWeight="Bold" />
</StackPanel>
</UserControl>
My shell view model is also quite simple. It inherits from Conductor<IPane>.Collection.OneActive, and takes in a list of panes that it adds to its Items collection:
public class ShellViewModel : Conductor<IPane>.Collection.OneActive
{
public ShellViewModel(IEnumerable<IPane> pages)
{
this.DisplayName = "Shell!";
this.Items.AddRange(pages);
}
}
Now, this is very it gets fuzzy for me. This is an excerpt from ShellView.xaml:
<controls:HamburgerMenu
ItemsSource="{Binding Items, Converter={StaticResource PaneListToHamburgerMenuItemCollection}}"
SelectedItem="{Binding ActiveItem, Mode=TwoWay, Converter={StaticResource HamburgerMenuItemToPane}}">
<ContentControl cal:View.Model="{Binding ActiveItem}" />
<controls:HamburgerMenu.ItemTemplate>
<DataTemplate>
<Grid x:Name="RootGrid"
Height="48"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="48" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<iconPacks:PackIconModern
Grid.Column="0"
Kind="{Binding Icon}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="16"
Foreground="White"
Text="{Binding Label}" />
</Grid>
</DataTemplate>
</controls:HamburgerMenu.ItemTemplate>
</controls:HamburgerMenu>
To make this work, I rely on two converters (who quite frankly do more than they should have to). One converter takes a ICollection<IPane> and creates a HamburgerMenuItemCollection with HamburgerMenuIconItems that are now contain a two-way link using the Tag properties of both the view model and the menu item.
class PaneListToHamburgerMenuItemCollection : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var viewModels = value as ICollection<IPane>;
var collection = new HamburgerMenuItemCollection();
foreach (var vm in viewModels)
{
var item = new HamburgerMenuIconItem();
item.Label = vm.DisplayName;
item.Icon = vm.Icon;
item.Tag = vm;
vm.Tag = item;
collection.Add(item);
}
return collection;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The second converter converts between the view model and the menu item using this Tag whenever the SelectedItem changes:
class HamburgerMenuItemToPane : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((IPane)value)?.Tag;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((HamburgerMenuIconItem)value)?.Tag;
}
}
When I run this code, and click the items in the hamburger menu, the page switches every time. The issue is that when the app first runs, there is no selected pane, and you cannot set one using any of the activation overrides available in ShellViewModel (such as OnViewAttached or OnActivate, or event the constructor), as the converter code that hooks up the Tag hasn't run yet.
My requirements for a working solution:
Caliburn's conductor must be in charge, as there are views and view models further down the stack that depend on the activation logic to run.
It should be possible to activate the first item from Caliburn at some point during the activation of ShellViewModel
Should respect separation of concerns, i.e. the view model should not know that a hamburger menu is being used in the view.
Please see the GitHub repository for a solution that should run straight away.
I believe the issue is caused by the HamburgerMenu_Loaded method inside the control. If there is a selected item before the control loads, the content of the hamburger menu is replaced:
private void HamburgerMenu_Loaded(object sender, RoutedEventArgs e)
{
var selectedItem = this._buttonsListView?.SelectedItem ?? this._optionsListView?.SelectedItem;
if (selectedItem != null)
{
this.SetCurrentValue(ContentProperty, selectedItem);
}
}
In your case, the ContentControl is removed and your Conductor cannot do its job.
I'm trying to see if this behavior can be changed in MahApps directly, by changing the code to something like this:
if (this.Content != null)
{
var selectedItem = this._buttonsListView?.SelectedItem ?? this._optionsListView?.SelectedItem;
if (selectedItem != null)
{
this.SetCurrentValue(ContentProperty, selectedItem);
}
}
In a UWP Project I have a UI which has an ItemsControl bound to a set of Team objects. There is a separate GameController object that has a CurrentTeam property which changes as the Game progresses. I want to be able to have a visual cue in the ItemTemplate for the Team that is the CurrentTeam. An example would be the Current Team's name gets underlined say. The Team objects do not have a reference to the GameController.
One way is to put a flag on each Team, say IsCurrentTeam and bind to that in the ItemTemplate. I don't particularly like this approach as it means when the CurrentTeam changes I've got to loop around all the Teams except the current one, to update their flags.
In WPF I think there might have been a solution using an ObjectDataProvider as it offers the ability to bind to a method, but since I'm in UWP this option is not available.
Does anyone know of a better approach to do this?
Ok, I've prepared an example that shows how this achievable. To work around limitations in UWP it uses a few techniques such as 'data context anchoring' and attached properties.
Here's my support classes, I assume they're somewhat similar to yours:
public class GameControllerViewModel : INotifyPropertyChanged
{
private Team _currentTeam;
public event PropertyChangedEventHandler PropertyChanged;
public GameControllerViewModel(IEnumerable<Team> teams)
{
Teams = teams;
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Team CurrentTeam
{
get { return _currentTeam; }
set
{
if (value != _currentTeam)
{
_currentTeam = value;
OnPropertyChanged();
}
}
}
public IEnumerable<Team> Teams { get; private set; }
}
public class Team
{
public string Name { get; set; }
}
And the code behind of the page:
public sealed partial class GamesPage : Page
{
public GamesPage()
{
this.InitializeComponent();
this.DataContext = new GameControllerViewModel(
new[]
{
new Team { Name = "Team A" },
new Team { Name = "Team B" },
new Team { Name = "Team C" },
new Team { Name = "Team D" }
}
);
}
}
As you can see, the constructor of the page instantiates a GameControllerViewModel with four teams and sets it as the data context of the page.
The page XAML is as follows:
<Page
x:Class="UniversalScratchApp.GamesPage" x:Name="View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UniversalScratchApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:BoolToFontWeightConverter x:Key="BoolToFontWeightConverter"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Current Team:" Margin="4" VerticalAlignment="Center"/>
<ComboBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Teams}" SelectedItem="{Binding CurrentTeam, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="4">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ItemsControl Grid.Row="1" Grid.ColumnSpan="2" ItemsSource="{Binding Teams}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" local:TeamProperties.CurrentTeam="{Binding ElementName=View, Path=DataContext.CurrentTeam}" local:TeamProperties.Team="{Binding}" FontWeight="{Binding Path=(local:TeamProperties.IsCurrentTeam), RelativeSource={RelativeSource Mode=Self}, Mode=OneWay, Converter={StaticResource BoolToFontWeightConverter}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Page>
In the DataTemplate of the ItemsControl you can see that I bind to a three custom attached properties; TeamProperties.CurrentTeam, TeamProperties.Team and TeamProperties.IsCurrentTeam. The attached properties are defined in the following class:
[Bindable]
public static class TeamProperties
{
private static void TeamPropertiesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
Team team = GetTeam(sender);
Team currentTeam = GetCurrentTeam(sender);
if (team != null && currentTeam != null)
{
SetIsCurrentTeam(sender, team.Equals(currentTeam));
}
}
public static readonly DependencyProperty CurrentTeamProperty = DependencyProperty.RegisterAttached("CurrentTeam", typeof(Team), typeof(TeamProperties), new PropertyMetadata(null, TeamPropertiesChanged));
public static Team GetCurrentTeam(DependencyObject obj)
{
return (Team)obj.GetValue(CurrentTeamProperty);
}
public static void SetCurrentTeam(DependencyObject obj, Team value)
{
obj.SetValue(CurrentTeamProperty, value);
}
public static readonly DependencyProperty TeamProperty = DependencyProperty.RegisterAttached("Team", typeof(Team), typeof(TeamProperties), new PropertyMetadata(null, TeamPropertiesChanged));
public static Team GetTeam(DependencyObject obj)
{
return (Team)obj.GetValue(TeamProperty);
}
public static void SetTeam(DependencyObject obj, Team value)
{
obj.SetValue(TeamProperty, value);
}
public static readonly DependencyProperty IsCurrentTeamProperty = DependencyProperty.RegisterAttached("IsCurrentTeam", typeof(bool), typeof(TeamProperties), new PropertyMetadata(false));
public static bool GetIsCurrentTeam(DependencyObject obj)
{
return (bool)obj.GetValue(IsCurrentTeamProperty);
}
public static void SetIsCurrentTeam(DependencyObject obj, bool value)
{
obj.SetValue(IsCurrentTeamProperty, value);
}
}
To explain, the CurrentTeam and Team properties are set on the dependency object (the textblock) by the bindings. While the Team property can use the current datacontext, the CurrentTeam property must be bound to the 'outer' DataContext. It does this by specifying an x:Name="View" on the Page and using that to 'anchor' the datacontext so it can then be accessed by bindings using the ElementName=View part of the binding.
So, whenever either of these properties change, the IsCurrentTeam property is set on the same dependency object by the TeamPropertiesChanged callback. The IsCurrentTeam property then is bound to the FontWeight property (as it was easier than underlining) with the BoolToFontWeightConverter shown here:
public class BoolToFontWeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool)
{
return ((bool)value) ? FontWeights.ExtraBold : FontWeights.Normal;
}
else
{
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
Now, when a team is selected in the top combobox (a proxy for whatever mechanism you use to change teams) the appropriate team in the ItemsControl will be displayed in bold.
Works nicely for me. Hope it helps.
In WPF my preferred option would be a DataTrigger, setting the underline property when the DataContext {Binding} of an item equals the contents of CurrentTeam on the parent (use an elementname to refer to that).
As UWP doesn't support Trigger, you can use a DataTriggerBehaviour like in this post.
I'm trying to have multiple ItemTemplates in a listview based on each items property. But I keep getting {"Error HRESULT E_FAIL has been returned from a call to a COM component."} in my value converter:
public class EquipmentTemplateConverter : IValueConverter
{
public object Convert(object value, Type type, object parameter, string language)
{
switch ((EquipmentType) (int) value)
{
case EquipmentType.Normal:
return Application.Current.Resources.FirstOrDefault(r => r.Key.ToString() == "EquipmentNormalTemplate");
case EquipmentType.Upgrade:
return Application.Current.Resources.FirstOrDefault(r => r.Key.ToString() == "EquipmentUpgradeTemplate");
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
public object ConvertBack(object value, Type type, object parameter, string language)
{
throw new NotImplementedException();
}
}
XAML:
<DataTemplate x:Key="EquipmentTemplate" >
<Grid>
<ContentControl DataContext="{Binding}" Content="{Binding}" x:Name="TheContentControl" ContentTemplate="{Binding Equipment.Type, Converter={StaticResource EquipmentTemplateConverter } }" />
</Grid>
</DataTemplate>
Any ideas how I can solve this?
The usual way to do this is by writing a DataTemplateSelector and assigning an instance of it to ContentControl.ContentTemplateSelector.
<DataTemplate x:Key="EquipmentTemplate" >
<DataTemplate.Resources>
<local:EquipmentTemplateSelector x:Key="EquipmentTemplateSelector" />
</DataTemplate.Resources>
<Grid>
<ContentControl
DataContext="{Binding}"
Content="{Binding}"
x:Name="TheContentControl"
ContentTemplateSelector="{StaticResource EquipmentTemplateSelector}"
/>
</Grid>
</DataTemplate>
C#:
public class EquipmentTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
// container is the container. Cast it to something you can call
// FindResource() on. Put in a breakpoint and use the watch window.
// I'm at work with Windows 7. Shouldn't be too hard.
var whatever = container as SomethingOrOther;
Object resKey = null;
// ************************************
// Do stuff here to pick a resource key
// ************************************
// Application.Current.Resources is ONE resource dictionary.
// Use FindResource to find any resource in scope.
return whatever.FindResource(resKey) as DataTemplate;
}
}
But I keep getting {"Error HRESULT E_FAIL has been returned from a call to a COM component."} in my value converter.
This error usually happens when a reference to a style or an event handler that dose not exist or is not within the context of the XAML.
You posted only the code of your converter and part of your xaml code, I can't 100% reproduce your data model and your xaml, but from your code, I think in your converter, you would like to return the specific DataTemplate, but you actually return a KeyValuePair<object, object>, resources are defined in ResourceDictionary follow the "key-value" parttern, for more information, you can refer to ResourceDictionary and XAML resource references.
Here I wrote a sample, again I didn't 100% reproduce your xaml and data model:
MainPage.xaml:
<Page.Resources>
<local:EquipmentTemplateConverter x:Key="EquipmentTemplateConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{x:Bind list}">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl DataContext="{Binding}" Content="{Binding}" ContentTemplate="{Binding Count, Converter={StaticResource EquipmentTemplateConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
code behind:
private ObservableCollection<EquipmentType> list = new ObservableCollection<EquipmentType>();
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
list.Add(new EquipmentType { Count = 0 });
list.Add(new EquipmentType { Count = 1 });
list.Add(new EquipmentType { Count = 0 });
list.Add(new EquipmentType { Count = 0 });
list.Add(new EquipmentType { Count = 1 });
list.Add(new EquipmentType { Count = 1 });
}
My EquipmentType class is quite simple:
public class EquipmentType
{
public int Count { get; set; }
}
and EquipmentTemplateConverter is like this:
public class EquipmentTemplateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
switch ((int)value)
{
case 0:
var a = Application.Current.Resources.FirstOrDefault(r => r.Key.ToString() == "EquipmentNormalTemplate");
return a.Value;
case 1:
var b = Application.Current.Resources.FirstOrDefault(r => r.Key.ToString() == "EquipmentUpgradeTemplate");
return b.Value;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Since you are using Application.Resources property in your converter, I just put the DataTemplate in the App.xaml for test:
<Application.Resources>
<DataTemplate x:Key="EquipmentNormalTemplate">
<Grid>
<TextBlock Text="This is EquipmentNormalTemplate." />
</Grid>
</DataTemplate>
<DataTemplate x:Key="EquipmentUpgradeTemplate">
<Grid>
<TextBlock Text="This is EquipmentUpgradeTemplate." />
</Grid>
</DataTemplate>
</Application.Resources>
But I agree with #Ed Plunkett, using DataTemplateSelector is a more common way to do this work.
Background Explanation
Okay so I'm currently binding a ContextMenu ItemsSource to an ObservableCollection of lets say TypeA
The following code is within the singleton class DataStore
private ObservableCollection<TypeA> TypeACollection = new ObservableCollection<TypeA>();
public ObservableCollection<TypeA> GetTypeACollection
{
get { return TypeACollection; }
}
public ObservableCollection<TypeA> GetFirstFiveTypeACollection
{
get
{
return TypeACollection.Take(5);
}
}
Now I've already successfully bound an ItemsControl to GetTypeACollection with the following XAML code:
The following code is within the MainWindow.xaml
<ItemsControl x:Name="TypeAList" ItemsSource="{Binding GetTypeACollection, Source={StaticResource DataStore}, UpdateSourceTrigger=PropertyChanged}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<User_Controls:TypeAUserControl Type="{Binding }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This works as expected, showing the TypeAUserControl, correctly formatted as intended with the data from TypeA
Now when i try to repeat this on a ContextMenu MenuItem by binding the ItemsSource to GetFirstFiveTypeACollection i initially see the expected results however upon deleting a TypeA object the MainWindow ItemsControl is updated where the ContextMenu is not.
I believe this is due to the fact that the binding itself is between the ContextMenu and a 'new' ObservableCollection<TypeA> (as seen in GetFirstFiveTypeAColletion ).
I have also attempted this through use of an IValueConverter
The following code is within the class ValueConverters
public class ObservableCollectionTypeAResizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<TypeA> TypeACollection = value as ObservableCollection<TypeA>;
return TypeACollection.Take(System.Convert.ToInt32(parameter));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
The XAML code I have tried.
Using IValueConverter
<MenuItem Header="First 5 Type A" Name="MI_FirstFiveTypeA"
ItemsSource="{Binding DATA_STORE.GetTypeACollection, ConverterParameter=5,
Converter={StaticResource ObservableCollectionTypeAResizeConverter},
Source={StaticResource DataStore}}"
/>
Using GetFirstFiveTypeACollection
<MenuItem Header="First 5 Type A" Name="MI_FirstFiveTypeA"
ItemsSource="{Binding DATA_STORE.RecentTimers, Source={StaticResource DataStore},
UpdateSourceTrigger=PropertyChanged}"
/>
I've no idea what to try and do next or how i should be doing this, Any help would be greatly appreciated!
Edit
Okay so I have changed the following
DataStore.cs
private ObservableCollection<TimerType> TimerTypes = new ObservableCollection<TimerType>();
public ObservableCollection<TimerType> getTimerTypes
{
get
{
return new ObservableCollection<TimerType>(TimerTypes.OrderByDescending(t => t.LastUsed));
}
}
public ObservableCollection<TimerType> RecentTimers
{
get
{
return new ObservableCollection<TimerType>(TimerTypes.OrderByDescending(t => t.LastUsed).Take(5));
}
}
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//THIS IS IN THE ADD METHOD
TimerTypes.Add(timer);
NotifyPropertyChanged("getTimerTypes");
NotifyPropertyChanged("RecentTimers");
NotifyPropertyChanged("TimerTypes");
//THIS IS IN THE REMOVE METHOD
TimerTypes.Remove(Timer);
NotifyPropertyChanged("getTimerTypes");
NotifyPropertyChanged("RecentTimers");
NotifyPropertyChanged("TimerTypes");
MainWindow.xaml
<ItemsControl x:Name="TimersList" ItemsSource="{Binding Path=getTimerTypes, UpdateSourceTrigger=PropertyChanged}" >
MainWindow.xaml.cs
//Lists are populated in DataStore.cs before this.
DataContext = DataStore.DATA_STORE;
InitializeComponent();
NotifyIcon.xaml
<MenuItem Header="Recent Timers" Name="MIRecent" ItemsSource="{Binding RecentTimers, UpdateSourceTrigger=PropertyChanged}"/>
NotifyIcon.xaml.cs
DataContext = DataStore.DATA_STORE;
InitializeComponent();
So everything binds correctly at start but when a TimerType is deleted the PropertyChangedEventHandler is always NULL. I thought this was a DataContext issue but I'm pretty sure I have the DataContext and all of the Bindings correct now?
Singleton Instance Creation
private static readonly DataStore Instance = new DataStore();
private DataStore() { }
public static DataStore DATA_STORE
{
get { return Instance; }
set { }
}
Unfortunately, by using Take (or any of the LINQ methods) you get a straight IEnumerable back. When you have bound to it, there is no INotifyCollectionChanged so changes to the collection will not cause the UI to update.
There's no real workaround due to the lack of INotifyCollectionChanged. Your best bet is to raise PropertyChanged against your "subset" property when you add/remove items to force the UI to renumerate the IEnumerable.
You might be able to solve this using a CollectionView
public class MyViewModel
{
CollectionView _mostRecentDocuments;
public MyViewModel()
{
Documents = new ObservableCollection<Document>();
_mostRecentDocuments = new CollectionView(Documents);
_mostRecentDocuments .Filter = x => Documents.Take(5).Contains(x);
}
public ObservableCollection<Document> Documents { get; private set; }
public CollectionView MostRecentDocuments
{
get
{
return _mostRecentDocuments;
}
}
}
I have created a user control that has a Label and a ComboBox. It is used like this:
<cc:LabeledComboBox
HeaderLabelContent="Months"
ItemsSource="{Binding AllMonths}"
SelectedValue="{Binding SelectedMonth}"/>
And here is what the UserControl XAML looks like:
<UserControl x:Class="CustomControls.LabeledComboBox" ...>
<UserControl.Resources>
<converters:MonthEnumToTextConverter x:Key="MonthEnumToTextConverter" />
</UserControl.Resources>
<DockPanel>
<Label x:Name="LblValue" DockPanel.Dock="Top"/>
<ComboBox x:Name="LstItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<!-- TODO: Fix so that the Converter can be set from outside -->
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</UserControl>
In the comment above you can see my problem. The control is generic (the ComboBox can contain pretty much anything) but on the Binding inside the DataTemplate I have specified a Converter that is very specific.
How can I specify the Converter from outside the UserControl?
I'm hoping for some kind of solution using a dependency property like this:
<cc:LabeledComboBox
...
ItemConverter="{StaticResource MonthEnumToTextConverter}"/>
You may have an internal binding converter that delegates its Convert and ConvertBack calls to one set is settable as dependency property:
<UserControl ...>
<UserControl.Resources>
<local:InternalItemConverter x:Key="InternalItemConverter"/>
</UserControl.Resources>
<DockPanel>
...
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding
Converter={StaticResource InternalItemConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</UserControl>
The internal converter could look like this:
class InternalItemConverter : IValueConverter
{
public LabeledComboBox LabeledComboBox { get; set; }
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (LabeledComboBox != null && LabeledComboBox.ItemConverter != null)
{
value = LabeledComboBox.ItemConverter.Convert(
value, targetType, parameter, culture);
}
return value;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (LabeledComboBox != null && LabeledComboBox.ItemConverter != null)
{
value = LabeledComboBox.ItemConverter.ConvertBack(
value, targetType, parameter, culture);
}
return value;
}
}
And finally the dependency property code like this:
public partial class LabeledComboBox : UserControl
{
private static readonly DependencyProperty ItemConverterProperty =
DependencyProperty.Register(
"ItemConverter", typeof(IValueConverter), typeof(LabeledComboBox));
public IValueConverter ItemConverter
{
get { return (IValueConverter)GetValue(ItemConverterProperty); }
set { SetValue(ItemConverterProperty, value); }
}
public LabeledComboBox()
{
InitializeComponent();
var converter = (InternalItemConverter)Resources["InternalItemConverter"];
converter.LabeledComboBox = this;
}
}
You can create multiple datatemplates for the the combobox items and then you can control what and how you want to display your comboxitem like below
<DataTemplate DataType="{x:Type vm:MonthDataTypeViewModel}" >
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
<DataTemplate DataType={x:Type vm:OtherViewModel}">
<TextBlock Text="{Binding}"/>
</DataTemplate>
If you do not have multiple viewmodels then you can use a template selector to select different data templates based on some property in your viewmodel.
OP here. Presenting the solution that I'll use until I find something better.
I don't specify only the Converter, but the whole DataTemplate:
<cc:LabeledComboBox>
<cc:LabeledComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
</cc:LabeledComboBox.ItemTemplate>
</cc:LabeledComboBox>
And here's the ItemTemplate dependency property:
public partial class LabeledComboBox : UserControl
{
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
"ItemTemplate",
typeof(DataTemplate),
typeof(LabeledComboBox),
new PropertyMetadata(default(DataTemplate), ItemTemplateChanged));
private static void ItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var labeledComboBox = (LabeledComboBox)d;
labeledComboBox.LstItems.ItemTemplate = (DataTemplate)e.NewValue;
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
// ...
}
EDIT: reworked to not use my personal example to avoid confusion ...
On your user control code behind you could define your dependency property.
I don't know what type your converters derive from so change 'myConverterType' to the type of converters you use.
public bool ItemConverter
{
get { return (myConverterType)GetValue(IntemConverterProperty); }
set { SetValue(ItemConverterProperty, value); }
}
public static readonly DependencyProperty ItemConverterProperty =
DependencyProperty.Register("ItemConverter", typeof(myConverterType),
typeof(LabeledComboBox), null);
In XAML you should then just be able to set the converter property as per your example. In my example it is used like this:
<cc:LabeledComboBox ItemConverter="{StaticResource theSpecificConverter}"/>
Then use this property, on your user control xaml, like this:
<ComboBox x:Name="LstItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={Binding ItemConverter, ElementName=UserControl}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>