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>
Related
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?
I have a grid like this:
<telerik:RadGridView x:Name="DataG"
ItemsSource="{Binding CamposUsu}"
SelectedItem="{Binding Selected}"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="False"
SelectionUnity="FullRow"
IsReadyOnly="True"
AutoGenerateColumns="False"
Loaded="DataG_Loaded" />
In the .cs file the method DataG_Loaded:
private void DataG_Loaded(object sender, RoutedEventesArgs e)
{
DataTemplate labelTemplate = new DataTemplate();
FrameworkElementFactory label = new FrameworkElementFactory(typeof(Label));
label.SetValue(Label.ContentProperty, "Unlimited");
labelTemplate.VisualTree = label;
labelTemplate.Seal();
this.DataG.Columns[7].CellTemplate = labelTemplate;
//this column 7 is a column called "Vl." with double values
}
Well, when I comment the method DataG_Loaded, my grid is fulfilled correctly with the objects I created on my viewmodel.
When I uncomment the method, the column "Vl." that had values like "93.5", "108.9"... is all fulfilled with the value "Unlimited".
This was already expected.
I want only the cells that the value is > 100.0 to turn to the string "Unlimited". For example:
Is there any way of doing this?
You could use a value converter on your data binding for that column in the XAML instead of using the Loaded event of your grid.
Example
Create a new class that inherits IValueConverter like this:
public class UnlimitedNumberConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (((float)value) > 100)
return "Unlimited";
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}
Add a resource to your XAML in order to access the converter:
<converters:UnlimitedNumberConverter x:Key="UnlimitedNumberConverter"/>
where "converters" is a namespace alias declared at the top of your XAML window/user control:
xmlns:converters="clr-namespace:MyApplication.Converters"
Then reference the converter in your data binding on the grid through XAML:
{Binding VI_Value, Converter={StaticResource UnlimitedNumberConverter}}
I have a listbox that is populated from a datatable. I want each item to have a specific image on listbox, but I want to set the image depending of an id that each item has.
for example , I have :
Products
Orange
Apple
ID
1
2
and the images are named: Item.1.png , Item.2.png
So, in my listbox,where I have apple, I will have next to it the image named: Item.2.png.
My problem is that I don't know how could I do a conditional binding . I don't want to have on my template hundreds of lines that are doing this for each item. I need to do this using a condition, like : if(product.id==1), Image.Source=Item.1.png.
Is there any way to do this in wpf?
It sounds to me like you need an IdToImageConverter that will decides which Image should be shown dependant on the value of the Id property. Something like this should do the trick:
[ValueConversion(typeof(int), typeof(ImageSource))]
public class IdToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(int) || targetType != typeof(ImageSource)) return false;
int id = (int)value;
if (id < 0) return DependencyProperty.UnsetValue;
string imageName = string.Empty;
switch (id)
{
case 1: imageName = "Item.1.png"; break;
case 2: imageName = "Item.2.png"; break;
}
if (imageName.IsEmpty()) return null;
return string.Format("/AppName;component/ImageFolderName/{0}", imageName);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
You could then use it in your ListBox.ItemTemplate something like this:
<YourConvertersXmlNamespacePrefix:IdToImageConverter x:Key="IdToImageConverter" />
...
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Id, Converter={StaticResource
IdToImageConverter}}" />
<TextBlock Text="{Binding Name}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As I understand your question, every object in the listbox has an ID property, where you want the image to be item.ID.png.
You could use a converter in your binding to do this. So in your listbox template, you can have something like:
// ... Listbox template
<Image Source={Binding pathToItemID, Converter={StaticResource MyConverter}/>
// ... Remaining ListBox template
You will need to add the converter to the UserControl's resources:
<UserControl.Resources>
<xmlnsPointingToConverter:MyConverter x:Key="MyConverter"/>
</UserControl.Resources>
Then add a MyConverter class which implements IValueConverter:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return string.Format("item.{0}.png", value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I am using WPF. I am using datagrid to dynamically add items in it.
When the application is initially loaded, the datagrid is empty, or when all the items in the datagrid are remove, it only shows datagrid header.
How can I remove the header, and show a message like "Please insert an item." when the datagrid is empty.
I'd use an IValueConverter for this. Bind directly to the Items source, and when it's null/empty, then return Visibility.Collapsed. Add text notice as a TextBlock, and negate the converter using a parameter.
<TextBlock Text="There are no items"
Visibility="{Binding Items,
Converter={StaticResource ItemsToVisibilityConverter},ConverterParameter=negate}" />
<DataGrid Visibility="{Binding Items,
Converter={StaticResource ItemsToVisibilityConverter}}">
</DataGrid>
And the converter has to make use of the ConverterParameter:
public class ItemsToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var items = value as IEnumerable<object>;
bool isVisible = items != null && items.Count() > 0;
if ((string)parameter == "negate") isVisible = !isVisible;
return isVisible ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
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.