Styling Items based on group sizes - c#

I am fairly new to WPF so forgive me if I am missing something obvious. I'm having a problem where I have a collection of AggregatedLabels and I am trying to bind the ItemCount of each AggregatedLabel to the FontSize in my DataTemplate so that if the ItemCount of an AggregatedLabel is large then a larger fontSize will be displayed in my listBox etc. The part that I am struggling with is the binding to the ValueConverter. Can anyone assist? Many thanks!
XAML Snippet
<DataTemplate x:Key="TagsTemplate">
<WrapPanel>
<TextBlock Text="{Binding Name, Mode=Default}"
TextWrapping="Wrap"
FontSize="{Binding ItemCount,
Converter={StaticResource CountToFontSizeConverter},
Mode=Default}"
Foreground="#FF0D0AF7"/>
</WrapPanel>
</DataTemplate>
<ListBox x:Name="tagsList"
ItemsSource="{Binding AggregatedLabels, Mode=Default}"
ItemTemplate="{StaticResource TagsTemplate}"
Style="{StaticResource tagsStyle}"
Margin="200,10,16.171,11.88" />

With your CollectionView in place you might be able to bind to the Groups property, i've never used that, will try it and clarify if possible...
Edit: Allright, here's one way to do it:
The data you bind to needs to be the CollectionView.Groups, the CollectionView should be defined like this:
CollectionView view = (ListCollectionView) CollectionViewSource.
GetDefaultView(LabelData);
view.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
Then you can bind to the respective properties of CollectionViewGroup in code, what you need are probably:
ItemCount
Name
That being said your original binding should work.
Note: You only pass one value to the converter, the ItemCount, thus it should look like this:
public class CountToFontSizeConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
const int minFontSize = 6;
const int maxFontSize = 38;
const int increment = 3;
if ((minFontSize + (int)value + increment) < maxFontSize)
{
return (double)(minFontSize + (int)value + increment);
}
return (double)maxFontSize;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Edit: Further clarifications...
Just add the CollectionView to your ViewModel as a property and create it in its constructor:
public class TagCloudViewModel//:INotifyPropertyChanged
{
public ObservableCollection<AggregatedLabelModel> AggregatedLabels
{get; set;}
public CollectionView AggregatedLabelsView {get; set;} // <-This...
public TagCloudViewModel()
{
var data = new DataAccess();
AggregatedLabels = data.GetData();
//...and this:
AggregatedLabelsView = (ListCollectionView)CollectionViewSource.
GetDefaultView(AggregatedLabels);
AggregatedLabelsView.GroupDescriptions.Add(
new PropertyGroupDescription("Name"));
}
}
Then bind to AggregatedLabelsView.Groups.

Related

UWP Display Index of ListViewItem

I've been searching for a solution to display the indexes of items from a ListView for a few hours. I can't add a new property to the data source, as an index property to bind to the value.
I've been trying to Bind to a Converter:
<DataTemplate x:Key="TubeTemplate" x:DataType="data:Tube">
<local:TubeTemplate HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
FavoritesNumber="{Binding Converter={StaticResource IndexConverter}}"
Closed="TubeTemplate_Closed"></local:TubeTemplate>
</DataTemplate>
This is the Converter:
public sealed class IndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var item = (ListViewItem)value;
var listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView;
int index = listView.IndexFromContainer(item) + 1;
return index.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
The issue is that my code breaks at: var item = (ListViewItem)value;
The value I'm getting is the DataType binded to each item, instead of the ListViewItem.
What Am I doing wrong?
Use {RelativeSource Mode=TemplatedParent} in the Binding. Then, you can get the ItemContainer with the help of VisualTreeHelper as follows.
<local:TubeTemplate ...
FavoritesNumber="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource IndexConverter}}"
.../>
and
public object Convert(object value, Type targetType, object parameter, string language)
{
var presenter = value as ListViewItemPresenter;
var item = VisualTreeHelper.GetParent(presenter) as ListViewItem;
var listView = ItemsControl.ItemsControlFromItemContainer(item);
int index = listView.IndexFromContainer(item) + 1;
return index.ToString();
}
However, index displayed in this way won't be automatically updated on collection changes. So, if you'll remove some items afterwards, you'd have to implement another function to request each item to reload its index.
Try using
AlternationIndex. Also according to this answer, you should use ListViewItem as RelativeSource
In your case, it would look something like
<DataTemplate>
<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource AncestorType=ListViewItem},
StringFormat={}Index is {0}}">
</TextBlock>
</DataTemplate>

WPF: Bind TabControl SelectedIndex to a View Model's Enum Property

I have a property on my ViewModel that is an enum:
ViewModel:
public MyViewModel {
// Assume this is a DependancyProperty
public AvailableTabs SelectedTab { get; set; }
// Other bound properties
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
}
public enum AvailableTabs {
Tab1,
Tab2,
Tab3
}
I'd like to be able to bind SelectedIndex (or SelectedItem) of my TabControl to this property and have it correctly set the appropriate tab using a converter. Unfortunately, I'm a bit stuck. I know I can easily just use the SelectedIndex in my model, but I want the flexibility of re-ordering the tabs without breaking anything. I've given each TabItem a Tag property of the applicable enum value.
My XAML:
<TabControl Name="MyTabControl" SelectedIndex="{Binding SelectedTab, Converter={StaticResource SomeConverter}}">
<TabItem Header="Tab 1" Tag="{x:Static local:AvailableTabs.Tab1}">
<TextBlock Text="{Binding Property1}" />
</TabItem>
<TabItem Header="Tab 2" Tag="{x:Static local:AvailableTabs.Tab2}">
<TextBlock Text="{Binding Property2}" />
</TabItem>
<TabItem Header="Tab 3" Tag="{x:Static local:AvailableTabs.Tab3}">
<TextBlock Text="{Binding Property3}" />
</TabItem>
</TabControl>
My problem is that I can't figure out how to get the TabControl into my converter so I can do:
// Set the SelectedIndex via the enum (Convert)
var selectedIndex = MyTabControl.Items.IndexOf(MyTabControl.Items.OfType<TabItem>().Single(t => (AvailableTabs) t.Tag == enumValue));
// Get the enum from the SelectedIndex (ConvertBack)
var enumValue = (AvailableTabs)((TabItem)MyTabControl.Items[selectedIndex]).Tag;
I'm afraid I might be overthinking it. I tried using a MultiValue converter without much luck. Any ideas?
You simply need a converter that casts the value to an index.
public class TabConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
return (int)value;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
return (AvailableTabs)value;
}
}
Instead of specifying the values in the XAML, I would bind ItemsSource to an array of values from your enum:
Code:
public AvailableTabs[] AvailableTabs => Enum.GetValues(typeof(AvailableTabs Enum)).Cast<AvailableTabs>().ToArray();
XAML:
<TabControl Name="MyTabControl" SelectedIndex="{Binding SelectedTab}" ItemsSource="{Binding AvailableTabs}" />
The important thing is that the solution should work if I change the tab order in the TabControl
if this is not important then the elements in the enum type
and the views in the TabControl have to be in the same order
the conversion in this case is to cast the enum value to int value
I name the views in TabControl according to enum values
AppTab.ValidDates (enum value) corresponds to validDatesView (view name)
enum values
public enum AppTab
{
Parameters, ValidDates, ...
}
views in TabControl
<TabControl Name="myTabControl" SelectedIndex="{Binding SelectedTab, Converter={c:AppTabToIntConverter}}"
IsEnabled="{Binding IsGuiEnabled}">
<TabItem Header="{x:Static r:Resource.Parameters}">
<view:ParametersView x:Name="parametersView"/>
</TabItem>
<TabItem Header="{x:Static r:Resource.Dates}">
<view:ValidDatesView x:Name="validDatesView"/>
</TabItem>
fill up ViewNameIndexDictionary and IndexViewNameDictionary
Window_Loaded event is too late, AppTabToIntConverter runs before that
public static Dictionary<AppTab, int> ViewNameIndexDictionary { get; set; } = new Dictionary<AppTab, int>();
public static Dictionary<int, AppTab> IndexViewNameDictionary { get; set; } = new Dictionary<int, AppTab>()
private void Window_Initialized(object sender, EventArgs e)
{
var i = 0;
foreach (TabItem item in myTabControl.Items)
{
var tabContentName = ((FrameworkElement)item.Content).Name;
// Convert TabItem name "validDatesView" to "ValidDates"
var appTabString = tabContentName.FirstCharToUpper().CutLastNCharacter("View".Length);
var appTab = (AppTab)Enum.Parse(typeof(AppTab), appTabString);
ViewNameIndexDictionary.Add(appTab, i);
IndexViewNameDictionary.Add(i, appTab);
i++;
}
}
the converter
// The root tag must contain: xmlns:c="clr-namespace:LedgerCommander.ValueConverter"
class AppTabToIntConverter : BaseValueConverter<AppTabToIntConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is AppTab appTab))
throw new Exception("The type of value is not AppTab");
return MainWindowView.ViewNameIndexDictionary[appTab];
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is int tabIndex))
throw new Exception("The type of value is not int");
return MainWindowView.IndexViewNameDictionary[tabIndex];
}
}
the BaseValueConverter (thanks to AngelSix)
public abstract class BaseValueConverter<T> : MarkupExtension, IValueConverter where T : class, new()
{
private static T Converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Converter ?? (Converter = new T());
}
public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}

Combox not updating with binding

Everytime i change my selected item inside my UI it's not updating my combobox.
XAML
<ComboBox x:Name="CompanyComboBox" HorizontalAlignment="Left" Height="26"
Margin="100,41,0,0" VerticalAlignment="Top" Width="144"
SelectionChanged="CompanyComboBox_SelectionChanged"
SelectedItem="{Binding SelectedCompany, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentPresenter
Content="{Binding Converter={StaticResource DescriptionConverter}}">
</ContentPresenter>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
C#
private Company _selectedCompany;
public Company SelectedCompany
{
get { return _selectedCompany; }
set
{
if (value == _selectedCompany)
return;
_selectedCompany = value;
OnPropertyChanged(nameof(SelectedCompany));
}
}
Just to clarify the Company class is actually an ENUM
DescriptionConverter:
public class CompanyDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var type = typeof(Company);
var name = Enum.GetName(type, value);
FieldInfo fi = type.GetField(name);
var descriptionAttrib = (DescriptionAttribute)
Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
return descriptionAttrib.Description;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
what i mean by inside my UI, is i have a list of companies set to a combobox item source, and when i change the combobox value to another company, it doesn't update in my source, it stay's as default.
My Enum might clarify the problem for someone:
[Description("Netpoint Solutions")]
Company1 = 0,
[Description("Blackhall Engineering")]
Company2 = 180,
Try to remove Mode=OneWayToSource and your event handler:
SelectionChanged="CompanyComboBox_SelectionChanged"
You don't need to handle the SelectionChanged event when you bind the SelectedItem property.
Also make sure that you set the DataContext to an instance of your class where the SelectedCompany property is defined.
Your Binding is defined as OneWayToSource. When you want it to be updated from the viewModel, you should set it to TwoWay.
See the documentation of Bindings for more details: https://msdn.microsoft.com/de-de/library/ms752347(v=vs.110).aspx
EDIT:
I skipped the part where you have an Enum as the source. I do not see, that you define the ItemsSource for the Combobox, where is this done? The value passed to the setter of SelectedCompany has to be in the Collection defined as ItemsSource.
For Enums, you can refer to this thread: How to bind an enum to a combobox control in WPF?

How to debug and solve the Binding issue with ComboBox?

I'm using ComboBox in my xaml but i'm unable to visualise any data on the view. It just shows empty text file and empty dropdown.
I have tried to debug the problem with help of these tips. However, I haven't been able to solve the issue.
Here's the databindingDebugConverter:
public class DatabindingDebugConverter : IValueConverter
{
public object Convert(object value1, Type targetType, object parameter, CultureInfo culture)
{
Debugger.Break();
return value1;
}
public object ConvertBack(object value2, Type targetType, object parameter, CultureInfo culture)
{
Debugger.Break();
return value2;
}
}
The Value1 is returning in ComboBox Text= case a "Field Device" (object{string})
and on the ItemsSource= value1 is returning object{Device} with the fields of Category and reference to Category1 object holding CategoryId in it.
For the SelectedValue a "Field Device" (object{string}) is once again returned.
Here is the ComboBox xaml:
<ComboBox x:Name="ProductCategoryComboBox" HorizontalAlignment="Right" Height="21.96" Margin="0,20,10.5,0" VerticalAlignment="Top" Width="100"
Text="{Binding DeviceDatabaseViewModel.SelectedDevice.Category, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource debugConverter}}"
IsEditable="False"
ItemsSource="{Binding DeviceDatabaseViewModel.SelectedDevice, Converter={StaticResource debugConverter}}"
SelectedValue="{Binding DeviceDatabaseViewModel.SelectedDevice.Category, Mode=TwoWay, Converter={StaticResource debugConverter}}"
SelectedValuePath="CategoryId"
DisplayMemberPath="Category" />
Similar binding including TextBlock fields within the xaml are working fine and displaying the string values from the SelectedDevice.
EDIT
The selectedDevice is referred from a dataGrid:
private Device _selectedDevice;
public Device SelectedDevice
{
get
{
return _selectedDevice;
}
set
{
if (_selectedDevice == value)
{
return;
}
_selectedDevice = value;
RaisePropertyChanged("SelectedDevice");
}
}
A Combobox is for choosing an Item out of a Collection (for example List or ObservableCollection if you want the UI to recognize changes in the collection).
You do not bind to a collection here:
ItemsSource="{Binding DeviceDatabaseViewModel.SelectedDevice, Converter={StaticResource debugConverter}}"
Instead of binding to the SelectedDevice you would need to bind to an ObservableCollection AllDevices or something like this to which you then could bind the ItemsSource.
Here an example for something you could bind to:
public class DeviceDatabaseViewModel
{
public ObservableCollection<Device> AllDevices
{
get; set;
}
public DeviceDatabaseViewModel()
{
AllDevices = new ObservableCollection<Device>();
AllDevices.Add(new Device { Category = 'Computer', CategoryId = 1 }, new Device { Category = 'Tablet', CategoryId = 2 });
}
}
Then using the following binding:
ItemsSource="{Binding DeviceDatabaseViewModel.AllDevices, Converter= {StaticResource debugConverter}}"

WPF ComboBox doesn't change the selected item

I'm a newbie in WPF, so it probably is something very basic that I'm forgetting to do but I can't see what it is.
I have a window with a combobox that display some data, I want the user to select a category in this combobox. It's working partially. The window show the combobox, starting with no selection, then the user choose a item, and it's set, but if the user try to change to other item, nothing works, it keeps the original selected item.
Here's me code:
[Category class]
public class Category {
public long CategoryId { get; set; }
public string Name { get; set; }
public Category MotherCategory { get; set; }
public ICollection<Category> Categories { get; set; }
public int Align { get; set; }
}
[ComboBox XAML]
<ComboBox Grid.Column="1" x:Name="motherCategoryComboBox" Margin="0,6,12,1"
IsSynchronizedWithCurrentItem="True">
<ComboBox.Resources>
<converter:LeftMarginConverter x:Key="LeftMarginConverter" />
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Categories}">
<TextBlock Text="{Binding Path=Name}" Margin="{Binding Path=Align, Converter={StaticResource LeftMarginConverter}}" />
</HierarchicalDataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
[Window code-behind file]
public CategoryWindow()
{
InitializeComponent();
db = new JaspeContext();
categorieslist = db.Categories.ToList();
motherCategoryComboBox.ItemsSource = categorieslist;
Title = "Add category";
}
[The converter]
public class LeftMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double leftMargin = double.Parse(value.ToString());
if (leftMargin != 1)
leftMargin = leftMargin * 9;
return new Thickness(leftMargin, 0, 0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}
Need your help. This is making me crazy!
Thanks!!
I hope I understood your question correctly. Is your DataContext a Category object? Sounds to me like you need to bind the SelectedItem property of the ComboBox.
E.g.:
<ComboBox Grid.Column="1" x:Name="motherCategoryComboBox" Margin="0,6,12,1"
IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding MotherCategory , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
It's not your case but since it happened to me I'm publishing this here, to help other people who might stumble upon this issue...
During the comboBox SelectionChangeCommitted() event handler I added the following line:
combobox.Text = combobox.Text.Trim();
what it did is reset the selectedIndex and selectedText properties and didn't allow them to change to the new value due to keyboard or mouse input.

Categories