I have a list with users that are ordered in some groups.
This is my ListBox xaml :
<ListBox x:Name="UserContainer"
ItemsSource="{Binding allUserViewModel.UserView}"
Background="Transparent"
HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
BorderThickness="0" Margin="0,0,0,7" Padding="0"
ItemContainerStyle="{DynamicResource ListBoxItemStyle}"
Visibility="{Binding allUserViewModel.UserView.Count, Converter={StaticResource ShowIfHasUsersConverter}}"
MouseEnter="UserContainer_OnMouseEnter" MouseLeave="UserContainer_OnMouseLeave">
<ListBox.Style>
<Style>
<Style.Triggers>
<Trigger Property="ListBox.IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Path=Name.IsExpanded}" Style="{StaticResource EditedMetroExpander}" Padding="0,3,0,3">
<Expander.Header>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" Foreground="Silver"
FontSize="18" FontFamily="Segoe UI Light" VerticalAlignment="Center" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<view:UserControlButton x:Name="UserControlButton" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is a list of users. Each user has a userviewmodel with a certain groupId to see in which group he should be.
So in my mainviewmodel which contains a list of those userviewmodels I set my groupdescriptions
public AllUserViewModel()
{
UserList = new ObservableCollection<UserViewModel>();
OnlineContacts = new List<UserViewModel>();
LocalContacts = new List<UserViewModel>();
UserView = (CollectionView)CollectionViewSource.GetDefaultView(UserList);
// Filter
if (UserView.CanFilter)
UserView.Filter = OnFilterUsers;
// Group
if (UserView.CanGroup && UserView.GroupDescriptions != null)
UserView.GroupDescriptions.Add(new PropertyGroupDescription(UserViewModel.GroupIdPropertyKey, new GroupDescriptionConverter()));
// Sorting
if (UserView.CanSort)
{
// Custom sort only available with ListCollectionView
ListCollectionView userListView = (ListCollectionView)CollectionViewSource.GetDefaultView(UserList);
userListView.CustomSort = new CustomUserComparer();
}
}
And this is my GroupDescriptionConverter
public class GroupDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var groupId = (int)value;
var group = GroupController.Instance.GetGroup(groupId);
return group.Name;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I want to bind the IsExpanded property on my expander on the ExpandGroup property of my group object.
public class Group
{
public int Id { get; set; }
public int OrderId { get; set; }
public string Name { get; set; }
public bool ExpandGroup { get; set; }
public bool NewLine { get; set; }
public Group()
{
}
}
A simple solution for this is to add a
private bool _isExpanded = true;
public bool IsExpanded
{
get { return _isExpanded; }
set { _isExpanded = value; }
}
property to your UserViewModel
and then do:
<Expander IsExpanded="{Binding Items[0].IsExpanded}">
on your template.
Simple and effective just like WPF should be
Related
I am trying to display a list with different object types (in the example dogs and cats). The types should be visually different (in the example realized by red and blue font in the DataTemplate in MainWindow.xaml).
Additionally I want that cats can neither be selected, nor that they get a colored background at the IsMouseOver trigger. However, I am not succeeding in the latter. I have left a few of my attempts as comments in MainWindow.xaml.
MainWindow.xaml
<Window.Resources>
<local:AnimalToFocusConverter x:Key="AnimalToFocusConverter" />
<local:AnimalToBackgroundColorConverter x:Key="AnimalToBackgroundColorConverter" />
<DataTemplate DataType="{x:Type local:Dog}">
<TextBlock Text="{Binding Path=Name}" Foreground="Red" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:Cat}">
<TextBlock Text="{Binding Path=Name}" Foreground="Blue" />
</DataTemplate>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding AnimalCollection}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent" />
<!--<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid Background="Transparent">
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding Converter={StaticResource AnimalToBackgroundColorConverter}}" />
</Trigger>
</Style.Triggers>-->
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
Animals.cs
public class Animals
{
public ObservableCollection<IAnimal> AnimalCollection { get; set; } = new ObservableCollection<IAnimal>();
public Animals()
{
AnimalCollection.Add(new Dog());
AnimalCollection.Add(new Dog());
AnimalCollection.Add(new Cat());
AnimalCollection.Add(new Dog());
AnimalCollection.Add(new Cat());
}
}
IAnimal.cs
public interface IAnimal
{
}
Dog.cs / Cat.cs
public class Dog : ObservableObject, IAnimal
{
private static int counter = 0;
public string Name { get; set; }
public Dog()
{
Name = $"Dog{++counter}";
}
}
Converter.cs
[ValueConversion(typeof(IAnimal), typeof(bool))]
public class AnimalToFocusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
Dog => true,
Cat => false,
_ => false,
};
}
// ...
[ValueConversion(typeof(IAnimal), typeof(SolidColorBrush))]
public class AnimalToBackgroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
Dog => new SolidColorBrush(Colors.LightBlue),
Cat => new SolidColorBrush(Colors.Transparent),
_ => new SolidColorBrush(Colors.LightBlue),
};
}
You could set the IsHitTestVisible property of the ListViewItem containers for the Cat objects to false:
<ListView ItemsSource="{Binding AnimalCollection}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="IsHitTestVisible"
Value="{Binding Converter={StaticResource AnimalToFocusConverter}}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
I want to mark a ListViewItem from a ListView by changing the foreground color of the textblock which is in the listviewitem. I want to do that dynamically. Whenever a user selects the item, the TextBlock's foreground color should be changed. Here is the Templete
<ListView Name="SongsListView"
IsItemClickEnabled="True"
ItemClick="SongsListView_ItemClick"
ItemsSource="{x:Bind rootpage.Songs}"
VerticalAlignment="Top"
HorizontalAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Song">
<controls:DropShadowPanel ShadowOpacity="0.20"
Color="Black"
HorizontalContentAlignment="Stretch"
BlurRadius="10"
OffsetX="0"
OffsetY="7.0">
<Grid HorizontalAlignment="Stretch" CornerRadius="5" Background="{ThemeResource SystemControlAltHighAcrylicElementBrush}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name}" Name="songNameTextBlock" TextWrapping="Wrap"/>
<TextBlock Text="{x:Bind Artist}" Name="ArtistNameTextBlock" TextWrapping="Wrap"/>
</StackPanel>
</Grid>
</controls:DropShadowPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="4"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
private void SongsListView_ItemClick(object sender, ItemClickEventArgs e)
{
//Please write the code for me !
}
I would suggest you to define a foreground relevant property in your Song class and bind it to the TextBlock's Foreground property. Then you could use Binding/{x:Bind} to change its foreground color when it's selected, instead of using the ItemClick event handler method to find the TextBlock controls in it.
Please refer to the following code sample for details:
<ListView Name="SongsListView"
IsItemClickEnabled="True"
ItemsSource="{x:Bind Songs}"
VerticalAlignment="Top"
HorizontalAlignment="Stretch" SelectedItem="{x:Bind currentSelectedItem,Mode=TwoWay,Converter={StaticResource myconverter}}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Song">
<controls:DropShadowPanel ShadowOpacity="0.20"
Color="Black"
HorizontalContentAlignment="Stretch"
BlurRadius="10"
OffsetX="0"
OffsetY="7.0">
<Grid HorizontalAlignment="Stretch" CornerRadius="5" Background="{ThemeResource SystemControlAltHighAcrylicElementBrush}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind Name}" Name="songNameTextBlock" TextWrapping="Wrap" Foreground="{x:Bind customcolor,Mode=OneWay}"/>
<TextBlock Text="{x:Bind Artist}" Name="ArtistNameTextBlock" TextWrapping="Wrap" Foreground="{x:Bind customcolor,Mode=OneWay}"/>
</StackPanel>
</Grid>
</controls:DropShadowPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="4"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Page.Resources>
<local:MyConverter x:Key="myconverter"></local:MyConverter>
</Page.Resources>
public sealed partial class MainPage : Page
{
public ObservableCollection<Song> Songs { get; set; }
private Song _currentSelectedItem;
public Song currentSelectedItem
{
get { return _currentSelectedItem; }
set
{
if (_currentSelectedItem != null)
{
_currentSelectedItem.customcolor = new SolidColorBrush(Colors.Black);
}
_currentSelectedItem = value;
_currentSelectedItem.customcolor = new SolidColorBrush(Colors.Red);
}
}
public MainPage()
{
this.InitializeComponent();
Songs = new ObservableCollection<Song>();
Songs.Add(new Song() {Name="abc",Artist="Singer1" });
Songs.Add(new Song {Name="def",Artist="Singer2" });
}
}
public class Song : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
RaisePropertyChanged("Name");
}
}
private string _Artist;
public string Artist
{
get { return _Artist; }
set
{
_Artist = value;
RaisePropertyChanged("Artist");
}
}
private SolidColorBrush _customcolor = new SolidColorBrush(Colors.Black);
public SolidColorBrush customcolor
{
get { return _customcolor; }
set
{
_customcolor = value;
RaisePropertyChanged("customcolor");
}
}
}
public class MyConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value as Song;
}
}
Please note that I make a converter class and use it when I bind currentSelectedItem to ListView's SelectedItem. Because I used x:Bind, it's compile time. If you do not add a converter, you will get error Invalid binding path 'currentSelectedItem' : Cannot bind type 'AppListView.model.Song' to 'System.Object' without a converter.
Besides, you could see I set Mode=OneWay when I use {x:Bind} to bind customcolor to TextBlock's foreground. It's due to the default value of {x:Bind}'s mode is OneTime. If you do not change OneTime to OneWay, you will not see the foreground color changed.
I have a Toolbox (Visual Studio alike) and a textbox where user can filter. The filter is working however the items in the ListBox always remain not expanded.
View.xaml
<ListBox x:Name="ListBox" Grid.Row="1" ItemsSource="{Binding Items}"
PreviewMouseLeftButtonDown="OnListBoxPreviewMouseLeftButtonDown" MouseMove="OnListBoxMouseMove"
Background="Transparent" BorderThickness="0" ScrollViewer.CanContentScroll="False">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<gem:ExpanderEx Header="{Binding Name}" IsExpanded="{Binding IsSelected, Mode=TwoWay}">
<ItemsPresenter />
</gem:ExpanderEx>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" FontSize="15"
Text="{Binding Path=Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="18 0 0 0" ToolTip="{Binding Help}">
<Image Source="{Binding IconSource, Converter={StaticResource NullableValueConverter}}" Margin="0 0 5 0" Width="16" />
<TextBlock Text="{Binding Name}" Padding="2" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel.cs
items = new BindableCollection<ToolboxItemViewModel>();
collectionView = CollectionViewSource.GetDefaultView(items);
collectionView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
collectionView.GroupDescriptions.Add(new PropertyGroupDescription("SubCategory"));
collectionView.Filter = ToolBoxFilter;
private readonly BindableCollection<ToolboxItemViewModel> items;
public IObservableCollection<ToolboxItemViewModel> Items { get { return items; } }
private string searchTerm;
public string SearchTerm
{
get { return searchTerm; }
set
{
if (searchTerm == value)
return;
searchTerm = value;
NotifyOfPropertyChange(() => SearchTerm);
//TODO: implement a defer here (Rx Extensions might help)
collectionView.Refresh(); //Refresh the filter.
}
}
ToolBoxItem.cs
public class ToolboxItemViewModel : PropertyChangedBase
{
private readonly ToolboxItem model;
public ToolboxItemViewModel(ToolboxItem model)
{
this.model = model;
IsSelected = false;
}
public ToolboxItem Model
{
get { return model; }
}
public string Name
{
get { return model.Name; }
}
public string Category
{
get { return model.Category; }
}
public string SubCategory
{
get { return model.SubCategory; }
}
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
NotifyOfPropertyChange(() => IsSelected);
}
}
How can I expand the grouped items when filtering?
Or another approach, how to expand the group item if there is any ListboxItem selected inside.
EDIT
Trying to figure it out, turns out I have a binding error on gem:ExpanderEx IsExpanded="{Binding IsSelected}"
BindingExpression path error 'IsSelected' property not found on 'object' 'CollectionViewGroupInternal'
In your collectionView filter, add yourObject.IsSelected = true;.
When you return true in your filter, you can alter the properties from your ToolboxItemViewModel object.
bool ToolBoxFilter(object item)
{
if (Search Criteria Match)
{
ToolboxItemViewModel model = (ToolboxItemViewModel)item;
model.IsSelected = true;
... rest of your code
}
}
I have got a little problem.
I use ListBox control with the textboxes.
I set focus on the first textbox and try to jump on the following textbox by the key tab.
It does not work.
What do I wrong?
Thanks in advance!
<ListBox Name="Box" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Background="Transparent" BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal" Margin="40,2,0,2">
<TextBlock Text="{Binding Label}" MinWidth="20" />
<TextBox TabIndex="{Binding Index, Mode=OneWay}" Text="{Binding Information, Mode=TwoWay}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var model = new List<Model>()
{
new Model() {Index = 1, Label = "1"},
new Model() {Index = 2, Label = "2"},
new Model() {Index = 3, Label = "3"},
new Model() {Index = 4, Label = "4"}
};
Box.ItemsSource = model;
}
}
public class Model
{
public int Index { get; set; }
public string Label { get; set; }
public string Information { get; set; }
}
}
You'll need to specify in the style how you want your tabs to work. You shouldn't need to bind the tabindex, unless you want to change up the order the tab works in. I think this should work similar to what you are trying to do:
<ListBox Name="Box"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Background="Transparent"
BorderThickness="0">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal"
Margin="40,2,0,2">
<TextBlock Text="{Binding Label}"
MinWidth="20" />
<TextBox Text="{Binding Information, Mode=TwoWay}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsTabStop"
Value="False" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="TabNavigation"
Value="Cycle" />
</Style>
</ListBox.Style>
</ListBox>
Consider below XAML =>
<fluent:Ribbon x:Name="MenuRibbon"
Title="title"
SnapsToDevicePixels="True">
<fluent:RibbonTabItem x:Name="Home"
Header="Home">
<fluent:RibbonGroupBox Header="Project">
<fluent:InRibbonGallery MinItemsInRow="3"
MaxItemsInRow="6"
Width="300"
ItemWidth="64"
ItemHeight="56"
ItemsSource="{Binding Projects}">
<fluent:InRibbonGallery.ItemTemplate>
<DataTemplate>
<DockPanel>
<Border BorderBrush="{x:Null}" Height="56">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" DockPanel.Dock="Bottom"
Text="{Binding Name}">
</TextBlock>
</Border>
</DockPanel>
</DataTemplate>
</fluent:InRibbonGallery.ItemTemplate>
</fluent:InRibbonGallery>
</fluent:RibbonGroupBox>
</fluent:RibbonTabItem>
</fluent:Ribbon>
I've bind ObservableColleciton of Projects to InRibbonGallery , And there is an instance of project (ActiveProject) exist in ViewModel.
a TextBlock defined in DataTemplate to display Name of Project object.
How can I change color of TextBlock that contains Active Project ?
ViewModel :
public class ViewModel : ViewModelBase
{
private ObservableCollection<Project> _projects;
public ViewModel()
{
Projects = new ObservableCollection<Project>(new List<Project>
{
new Project {Id = "0", Name = "Project1"},
new Project {Id = "1", Name = "Project2"},
new Project {Id = "2", Name = "Project3"}
});
Project = Projects[0];
}
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
_projects = value;
RaisePropertyChanged(() => Projects);
}
}
public Project Project { get; set; }
}
public class Project : ObservableObject
{
private string _id;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
public string Id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Project files located here .
You can do this by using a MultiBinding and a DataTrigger in combination for a better result.
so your xaml would look like:
<Window.Resources>
<vm:ViewModel x:Key="ViewModel" />
<vm:ActiveProjectCheckConverter x:Key="ActiveProjectCheckConverter" />
</Window.Resources>
...
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
DockPanel.Dock="Bottom"
Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background"
Value="Transparent" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ActiveProjectCheckConverter}">
<Binding Path="Name" />
<Binding Path="DataContext.ActiveProject.Name"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type fluent:InRibbonGallery}}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="1,0">
<GradientStop Offset="0.0"
Color="#00FFFFFF" />
<GradientStop Offset="1.1"
Color="#FFFFFFFF" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
and your converter:
public class ActiveProjectCheckConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
string first = values[0] as string;
string second = values[1] as string;
return !string.IsNullOrEmpty(first) && !string.IsNullOrEmpty(second) && first == second;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Now I did make one more change, your Project object in your ViewModel, if you want changes to that to reflect in the View you need to make that implement INPC itself. So I did update that and also renamed it to ActiveProject
private Project _activeProject;
public Project ActiveProject {
get {
return _activeProject;
}
set {
if (value == _activeProject)
return;
_activeProject = value;
RaisePropertyChanged(() => ActiveProject);
}
}
Update
You can find the above updates at: Dropbox-Link