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.
Related
I have a WPF window with a listview with textboxes in it, and I want to enable users to switch between textboxes with the TAB key. I created a function that does that, but every time the textbox loses focus, I refresh the listview and so the listview itself gets focused.
I know the problem occurs because of the refresh event because commenting that part will result in the correct element (the textbox) being focused.
I have tried many alternative solutions but none works (prevent item losing focus when refreshing list view in c#; Focus on TextBox after ListView 'SelectionChanged' Event).
It seems there is something problematic in focusing on an that element after the listview is refreshed.
I have tried remembering the index of the item and then focusing on it, after the TAB key was pressed. tried also remembering the focused control and then focusing on it after the refresh.
private void RTB_Reference_LostFocus(object sender, RoutedEventArgs e)
{
int Index = DetailsList.SelectedIndex;
Index = DetailsList.Items.IndexOf(DetailsList.SelectedItem);
try
{
//Get cell value by using sender Object
string inTime = ((System.Windows.Controls.TextBox)sender).Text;
DetailItem item = (DetailItem)DetailsList.Items[Index];
item.Reference = inTime;
UpdateExplanation(item);
}
catch (Exception)
{
}
}
private void RTB_Detail_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
e.Handled = true;
//System.Windows.MessageBox.Show("Tab");
int Idx = DetailsList.SelectedIndex;
System.Windows.Controls.ListViewItem lvi = (System.Windows.Controls.ListViewItem)DetailsList.ItemContainerGenerator.ContainerFromItem(DetailsList.SelectedItem);
GUF.FocusItem(DetailsList, Idx, "RTB_Detail");
//IsLastKeyTAB = true;
}
//else
// IsLastKeyTAB = false;
}
private void UpdateExplanation(DetailItem item)
{
item.Explanation = GetExplanation(item.Reference, item.Detail);
IInputElement focusedControl = Keyboard.FocusedElement;
DetailsList.Items.Refresh();
focusedControl.Focus();
RefreshDetailsList(DetailsList, IsEnglish);
}
expected result is to keep focus on that textbox after the refresh. that does not occur...
EDIT 1
This is the listview xaml:
<ListView FlowDirection="RightToLeft" x:Name="DetailsList" VirtualizingStackPanel.IsVirtualizing="False"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
HorizontalContentAlignment="Center" ScrollViewer.VerticalScrollBarVisibility="Visible" Padding="0 0 0 25"
AllowDrop="True"
ItemsSource="{Binding DetailItem}"
Loaded="ListView_Loaded"
Margin="26,157,23,0"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}" Height="599">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="Margin" Value="4, 4, 4, 4"/>
<Setter Property="FontWeight" Value="DemiBold"/>
<Setter Property="Height" Value="22"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderBrush="Transparent" BorderThickness="0" Background="{TemplateBinding Background}">
<GridViewRowPresenter HorizontalAlignment="Stretch" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Width="Auto" Margin="0" Content="{TemplateBinding Content}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- <Setter Property="Background" Value="#6B54FF"/> -->
<Setter Property="Foreground" Value="#6B57FF"/>
<Setter Property="BorderThickness" Value="2" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="#6B57FF"/>
<Setter Property="Foreground" Value="#6B57FF" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Width="50" Header="סימון" DisplayMemberBinding="{Binding Mark}"/>
<!--
<GridViewColumn Width="30" >
<GridViewColumn.CellTemplate>
<DataTemplate >
<Button Style="{StaticResource PlusButtonStyle}" x:Name="buttonPlusDocument" Click="buttonPlusDocument_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
-->
<GridViewColumn Header="הפניה במסמך" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="RTB_Reference" BorderBrush="#5f27ff" BorderThickness="1" KeyDown="RTB_Reference_KeyDown" HorizontalAlignment="Stretch" Height="20" Margin="0" Padding="0" FontSize="12" IsEnabled="True"
LostFocus="RTB_Reference_LostFocus" GotFocus="RTB_Reference_GotFocus">
<TextBox.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="2"/>
<Setter Property="BorderBrush" Value="#5f27ff"/>
<Setter Property="BorderThickness" Value="1" />
</Style>
</TextBox.Resources>
</TextBox>
<!--DataContext="{Binding SelectedItem, ElementName=ListViewAppendixNameList, Mode=TwoWay}"-->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="פרט" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="RTB_Detail" BorderBrush="#5f27ff" BorderThickness="1" HorizontalAlignment="Stretch" Height="20" Margin="0" Padding="0" FontSize="12" IsEnabled="True"
KeyDown="RTB_Detail_KeyDown" LostFocus="RTB_Detail_LostFocus" GotFocus="RTB_Detail_GotFocus">
<TextBox.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="2"/>
<Setter Property="BorderBrush" Value="#5f27ff"/>
<Setter Property="BorderThickness" Value="1" />
</Style>
</TextBox.Resources>
</TextBox>
<!--DataContext="{Binding SelectedItem, ElementName=ListViewAppendixNameList, Mode=TwoWay}"-->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="הסבר" Width="350" DisplayMemberBinding="{Binding Explanation}"/>
<GridViewColumn Width="30" >
<GridViewColumn.CellTemplate>
<DataTemplate >
<Button Style="{StaticResource DeleteButtonStyle}" x:Name="BT_DeleteDetail" Click="BT_DeleteDetail_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
This is the details class:
public class DetailItem
{
public string Mark { get; set; }
public string Reference { get; set; }
public string Detail { get; set; }
public string Explanation { get; set; }
}
I refresh the listview so that the Explanation text will be updated.
I don't went through your whole code but only those parts that are relevant to your question. To simplify things, I will use the attached property TabNavigation and the data binding feature. The binding will automatically update the control values so that the refreshing of the ListView and the focus handling becomes redundant.
Remark: To shorten the XAML code I will only show the relevant code portions.
Tab navigation
Set the attached property KeyboardNavigation.TabNavigation on the ListView to Continue so that the focus will loop through the list:
<ListView x:Name="DetailsList"
KeyboardNavigation.TabNavigation="Continue"
...
</ListView>
You can control which elements can receive focus on Tab key pressed by setting the property IsTabStop on the elements. The default value is True. So set it to False on those elements you wish to exclude. It's useful to disable Tab focus on the ListViewItem itself so that the Tab key is only effective for the containing controls:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsTabStop"
Value="False" />
...
</Style>
</ListView.ItemContainerStyle>
If you wish to exclude the Button as well, so that you only jump between the TextBox elements, set the Button's IsTabStopproperty to False as well:
<GridViewColumn Width="30">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button x:Name="BT_DeleteDetail"
IsTabStop="False"
...
/>
...
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
If you also wish to change the order the elements will receive focus on Tab key pressed, you can do it by setting the element's TabIndex property. A control with a lower tab index receives focus before a control with a higher index.
Binding the TextBox
To enable binding to a data model it is required that the data model (view model) implements INotifyPropertxChanged:
public class DetailItem : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private string mark;
public string Mark
{
get => this.mark;
set
{
if (value == this.mark) return;
this.mark = value;
OnPropertyChanged();
}
}
private string reference;
public string Reference
{
get => this.reference;
set
{
if (value == this.reference) return;
this.reference = value;
OnPropertyChanged();
}
}
private string detail;
public string Detail
{
get => this.detail;
set
{
if (value == this.detail) return;
this.detail = value;
OnPropertyChanged();
}
}
private string explanation;
public string Explanation
{
get => this.explanation;
set
{
if (value == this.explanation) return;
this.explanation = value;
OnPropertyChanged();
}
}
}
In order access the view model from your DataTemplate set its DataType property to the type of the view model (DetailItem). Remember to always set the DataType of a DataTemplate. Then add the Binding to the TextBox. Note that the Mode (direction) of the binding is set to OneWayToSource. This limits the binding to set data from TextBoxto DetailItem only:
<GridViewColumn.CellTemplate>
<DataTemplate DataType="viewModels:DetailItem">
<TextBox x:Name="RTB_Reference"
Text="{Binding Reference, Mode=OneWayToSource}"
...
</TextBox>
...
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumn.CellTemplate>
<DataTemplate DataType="viewModels:DetailItem">
<TextBox x:Name="RTB_Detail"
Text="{Binding Detail, Mode=OneWayToSource}"
...
</TextBox>
...
</DataTemplate>
</GridViewColumn.CellTemplate>
The final step is to update the DetailItem.Explanation property. To do this, we move the UpdateExplanation() method into the view model DetailItem. Since we use binding we can now get rid of all the ListView refresh and focus logic inside, so the method becomes smaller. Note that since we moved the method into the DetailItem class, we can also remove the parameter of the method:
private void UpdateExplanation()
{
this.Explanation = GetExplanation(this.Reference, this.Detail);
}
The UpdateExplanation() method is invoked directly from the setter of the Reference and Detail property, so that everytime these properties are changed the Explanation value will be updated. With the updated Reference and Detail setter the final version of the DetailItem class will then look as followed:
public class DetailItem : INotifyPropertyChanged
{
private void UpdateExplanation()
{
this.Explanation = GetExplanation(this.Reference, this.Detail);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private string mark;
public string Mark
{
get => this.mark;
set
{
if (value == this.mark) return;
this.mark = value;
OnPropertyChanged();
}
}
private string reference;
public string Reference
{
get => this.reference;
set
{
if (value == this.reference) return;
this.reference = value;
OnPropertyChanged();
UpdateExplanation();
}
}
private string detail;
public string Detail
{
get => this.detail;
set
{
if (value == this.detail) return;
this.detail = value;
OnPropertyChanged();
UpdateExplanation();
}
}
private string explanation;
public string Explanation
{
get => this.explanation;
set
{
if (value == this.explanation) return;
this.explanation = value;
OnPropertyChanged();
}
}
}
Try Below Code. Enjoy !!!
<Grid>
<ListView Margin="45.5,47.5,41.167,0" Name="lvtestListview" Height="188.333" VerticalAlignment="Top">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<!--<Setter Property="IsSelected" Value="{Binding IsGroovy}"/>-->
<Style.Triggers>
<Trigger Property="IsSelected" Value="true" >
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Background" Value="LightGreen" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Item Code" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
<TextBlock Text="{Binding ItemCode}" Margin="6,3"/>
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Item Name" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
<TextBox Loaded="txtBarcode_Loaded" Text="{Binding ItemName}" Name="txtBarcode" Focusable="{Binding IsFocused}" Cursor="IBeam" />
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Next Item" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
<TextBox TabIndex="3" Name="txtgvPL1" KeyDown="txtgvPL1_KeyDown" Text="{Binding PL1}" Cursor="IBeam"/>
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
code behind
public class ClassA : INotifyPropertyChanged
{
public string ItemCode { get; set; }
public string ItemName { get; set; }
private bool isFocused;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsFocused
{
get
{
return isFocused;
}
set
{
if (value != isFocused)
{
isFocused = value;
RaisePropertyChanged("IsFocused");
}
}
}
private void RaisePropertyChanged(string propName)
{
PropertyChangedEventHandler eh = PropertyChanged;
if (eh != null)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
public partial class MainWindow : Window
{
List<ClassA> lstClassA = new List<ClassA>();
public MainWindow()
{
InitializeComponent();
BindGrid();
}
private void BindGrid()
{
ClassA ca = new ClassA();
ca.ItemCode = "A";
ca.IsFocused = true;
lstClassA.Add(ca);
lvtestListview.ItemsSource = lstClassA;
}
private void txtBarcode_Loaded(object sender, RoutedEventArgs e)
{
Keyboard.Focus(((TextBox)sender));
}
private void txtgvPL1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter )
{
ClassA ca = new ClassA();
ca.ItemCode = "B";
ca.IsFocused = true;
lstClassA.Add(ca);
foreach (ClassA a in lstClassA)
{
if (a.ItemCode == "A")
{
a.IsFocused = false;
}
}
lvtestListview.ItemsSource = lstClassA;
lvtestListview.Items.Refresh();
}
}
}
I have a tree view built with HierarchicalDataTemplates, I want to be able to add JSON files to SegmentInfo nodes - if I do so, the data is added but the change is not reflected in UI (still the comment says "no data" in red).
I've made the list the tree view items as ObservableCollection, moved it to a "ViewModel" class that inherits INotifyPropertyChanged, I seem to set it up properly, I've set DataContext to the ViewModel object in my Window.
In xaml I've set the bindings and mode as TwoWay. Still nothing helped
XAML:
<Window.Resources>
<local:BoolToStringConverter x:Key="BoolToStringConverter" FalseValue="no data" TrueValue="has data" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" MinHeight="384.8"/>
<RowDefinition Height="35.2"/>
</Grid.RowDefinitions>
<TreeView Name="trvTypeInfos" Margin="5" Grid.Row="0" ItemsSource="{Binding Path=TypeInfoList, Mode=TwoWay}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="ListBoxItem.PreviewMouseUp"
Handler="ListBoxItem_PreviewMouseUp"/>
<Setter Property="IsExpanded" Value="True"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type data:TypeInfo}" ItemsSource="{Binding SegmentInfos, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding SegmentInfos.Count}" Foreground="Blue"/>
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:SegmentInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" ("/>
<TextBlock Text="{Binding Path=HasData, Mode=TwoWay, Converter={StaticResource BoolToStringConverter}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Text" Value="no data">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
<Trigger Property="Text" Value="has data">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text=")"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="80" Height="20" Content="OK" Margin="5,0, 5, 5" IsDefault="True" Click="OK_Click"/>
<Button Width="80" Height="20" Content="Cancel" Margin="5,0, 5, 5" Click="Cancel_Click" />
</StackPanel>
</Grid>
Window class:
public SegmentDataUpdaterDialog(SegmentDataUpdater segmentDataUpdater, List<TypeInfo> typeInfoList)
{
ViewModel = new ViewModel(typeInfoList);
DataContext = ViewModel;
SegmentDataUpdater = segmentDataUpdater;
InitializeComponent();
}
private void ListBoxItem_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
SegmentInfo segInfo = item.Header as SegmentInfo;
if (segInfo != null)
{
MessageBox.Show(segInfo.JsonContents);
var filePath = AskForFile();
bool success = SegmentDataUpdater.TryStoreJson(segInfo, filePath, out string json);
if (success)
{
segInfo.JsonContents = json;
segInfo.HasData = true;
}
}
}
ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<TypeInfo> _typeInfoList;
public ObservableCollection<TypeInfo> TypeInfoList
{
get { return _typeInfoList; }
set
{
if (_typeInfoList==null || !value.All(_typeInfoList.Contains))
{
_typeInfoList = value;
OnPropertyChanged(nameof(TypeInfoList));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(List<TypeInfo> typeInfos)
{
TypeInfoList = new ObservableCollection<TypeInfo>(typeInfos);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
TypeInfo class:
public class TypeInfo
{
public string Name { get; set; }
public ObservableCollection<SegmentInfo> SegmentInfos { get; set; }
public int ElementId { get; set; }
public TypeInfo()
{
SegmentInfos = new ObservableCollection<SegmentInfo>();
}
}
SegmentInfo class:
public class SegmentInfo
{
public string Name { get; set; }
public bool HasData { get; set; }
public string JsonContents { get; set; }
public int ElementId { get; set; }
}
Converter classes:
public class BoolToValueConverter<T> : IValueConverter
{
public T FalseValue { get; set; }
public T TrueValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseValue;
else
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null ? value.Equals(TrueValue) : false;
}
}
public class BoolToStringConverter : BoolToValueConverter<String> { }
I expect that after successful adding json file to the SegmentInfo the UI will update the node with "has data" comment.
Now I can check that the data is really added to the SegmentInfo but UI doesn't reflect that.
Your HasData property does not update the UI, as you have no mechanism to update it (INotifyPropertyChanged). SegmentInfo needs to implement INotifyPropertyChanged.
If you plan to have a property Bind to the UI, it needs to have an PropertyChanged Notification go out for it individually. So on your SegmentInfo class; Name, HasData, and JsonContent should each raise an OnPropertyChanged event in their setter.
A good way to think of it; anything that is directly bound in XAML (Text="{Binding Name}") should raise an event when changed. If you bind any properties like: (Text="{Binding MyThing.Name}") you will not get an update when MyThing.Name changes. You need to break out the property and Notify on it directly.
I've simply done the following:
public string X
{
get { return _X; }
set
{
_X= value;
NotifyOfPropertyChange("X");
}
}
XAML:
<ListBox SelectedItem="{Binding X}" Grid.Row="0" Grid.Column="2" SelectedIndex="0" Margin="0 10 0 0">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource MaterialDesignToolToggleListBoxItem}">
<Setter Property="Padding" Value="8 12 8 12" />
</Style>
</ListBox.Resources>
<ListBoxItem>
<TextBlock Text="TEXT1"/>
</ListBoxItem>
<ListBoxItem>
<TextBlock Text="TEXT2"/>
</ListBoxItem>
<ListBoxItem>
<TextBlock Text="TEXT3"/>
</ListBoxItem>
</ListBox>
Instead of getting the item only, I'm getting the following:
System.Windows.Controls.ListBoxItem: SELECTED_ITEM_HERE
You have to cast the the textblock and set _x the text
private string _X;
public object X
{
get { return _X; }
set
{
var a = ((ListBoxItem)value).Content as TextBlock;
_X = a.Text;
NotifyOfPropertyChange("X");
}
}
instead specifying listview items directly in xaml, you can bind those values to itemssource.
i have tried following code snippet
public Class1()
{
TextList = new ObservableCollection<string> { "TEXT1", "TEXT2" };
}
private string _X;
public string X
{
get { return _X; }
set
{
_X = value;
if (PropertyChanged != null)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("X"));
}
}
public ObservableCollection<string> TextList { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
in xaml,
<ListBox SelectedItem="{Binding X}" ItemsSource="{Binding TextList}"/>
with this i am getting only "TEXT2"/"TEXT1" instead with control type .
Instead of adding ListBoxItems to the ListBox, you should add strings to it:
<ListBox SelectedItem="{Binding X}" Grid.Row="0" Grid.Column="2" SelectedIndex="0" Margin="0 10 0 0"
xmlns:s="clr-namespace:System;assembly=mscorlib">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource MaterialDesignToolToggleListBoxItem}">
<Setter Property="Padding" Value="8 12 8 12" />
</Style>
</ListBox.Resources>
<s:String>TEXT1</s:String>
<s:String>TEXT2</s:String>
<s:String>TEXT3</s:String>
</ListBox>
The other option would be to change the type of your source property to ListBoxItem but that's not what you want. The type of the item and the source property of the property that you are binding to SelectedItem must match.
I'm using Visual Studio 2015, I'm trying to teach myself the MVVM pattern, and I'm hitting a road block. My code is loosely based off of Josh Smiths article, I'm using it to help me learn MVVM and create a small app for work in the process.
What I'm trying to accomplish:
I've bound a viewmodel to a listview showing a list of products, each product has a list of "productTemplate" items. In my View I would like this list to populate inside a listbox when a product from my list view is selected. I am implementing INotifyPropertyChanged. I think I'm just missing something simple but I'm not sure.
My code:
Two Models (Product, ProductTemplateItem);
public class Product {
private string _productNum;
private string _productFamily;
public Product() {
}
public string ProductNum { get; set; }
public string ProductFamily { get; set; }
}
public class ProductTemplateItem : ChangeEventHandlerBase {
private string _TemplateItem;
private string _TemplateCode;
public ProductTemplateItem(string templateItem, string templateCode) {
_TemplateItem = templateItem;
_TemplateCode = templateCode;
}
public string TemplateItem {
get { return _TemplateItem; }
set { if(_TemplateItem != value) {
_TemplateItem = value;
OnPropertyChanged("TemplateItem");
}
}
}
public string TemplateCode {
get { return _TemplateCode; }
set {
if (_TemplateCode != value) {
_TemplateCode = value;
OnPropertyChanged("TemplateCode");
}
}
}
public override string DisplayName {
get {
return $"{TemplateItem} - {TemplateCode}";
}
}
}
My ViewModels (Product View Model, brings everything together and adds the product template list, and AlProductsViewModel adds data and exposes everything to be bound in XAML):
public class ProductViewModel : BaseViewModel {
private Product _product;
private bool _isSelected;
private List<ProductTemplateItem> _productTemplate;
public ProductViewModel(string productNum, string productFamily) {
Product.ProductNum = productNum;
Product.ProductFamily = productFamily;
_productTemplate = new List<ProductTemplateItem>();
}
public string ProductNumber {
get { return _product.ProductNum; }
set { if(_product.ProductNum != value) {
_product.ProductNum = value;
OnPropertyChanged("ProductNumber");
}
}
}
public string ProductFamily {
get { return _product.ProductFamily; }
set {
if (_product.ProductFamily != value) {
_product.ProductFamily = value;
OnPropertyChanged("ProductFamily");
}
}
}
public bool IsSelected {
get { return _isSelected; }
set {
if (_isSelected != value) {
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public List<ProductTemplateItem> ProductTemplate {
get { return _productTemplate; }
set { if (_productTemplate != value) {
_productTemplate = value;
OnPropertyChanged("ProductTemplate");
}
}
}
public Product Product {
get {
if (_product == null) {
_product = new Product();
return _product;
}
else {
return _product;
}
}
set { if(_product != value) {
_product = value;
OnPropertyChanged("Product");
}
}
}
public override string DisplayName {
get {
return Product.ProductNum;
}
}
}
public class AllProductsViewModel : BaseViewModel{
public AllProductsViewModel() {
AddProducts();
}
private void AddProducts() {
List<ProductViewModel> all = new List<ProductViewModel>();
all.Add(new ProductViewModel("4835", "Crop Cart"));
all.Add(new ProductViewModel("780", "Piler"));
all.Add(new ProductViewModel("880", "Piler"));
all.Add(new ProductViewModel("150", "Scooper"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Miscellaneous","MISC"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Drawbar", "DRBR"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Mainframe", "FRAM"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Conveyor", "CONV"));
all[1].ProductTemplate.Add(new Model.ProductTemplateItem("Hello", "HELL"));
AllProducts = new ObservableCollection<ProductViewModel>(all);
}
public ObservableCollection<ProductViewModel> AllProducts { get; private set; }
}
And my XAML code which is a user control with a ListView based off of Josh's code and a listbox that needs to be updated based off of the selection in the ListView:
<UserControl x:Class="Parts_Book_Tool.Views.ProductsView"
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"
xmlns:local="clr-namespace:Parts_Book_Tool.Views"
xmlns:viewModel="clr-namespace:Parts_Book_Tool.ViewModel"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewModel:AllProductsViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<CollectionViewSource x:Key="ProductGroups" Source="{Binding Path=AllProducts}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ProductFamily"/>
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ProductFamily" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<GroupStyle x:Key="ProductGroupStyle">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
FontWeight="Bold"
Margin="1"
Padding="4,2,0,2"
Text="{Binding Path=Name}"
/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<Style x:Key="MainHCCStyle" TargetType="{x:Type HeaderedContentControl}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Border
Background="{StaticResource Brush_HeaderBackground}"
BorderBrush="LightGray"
BorderThickness="1"
CornerRadius="5"
Margin="4"
Padding="4"
SnapsToDevicePixels="True"
>
<TextBlock
FontSize="14"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
Text="{TemplateBinding Content}"
/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ProductItemsStyle" TargetType="{x:Type ListViewItem}">
<!--
Stretch the content of each cell so that we can
right-align text in the Total Sales column.
-->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<!--
Bind the IsSelected property of a ListViewItem to the
IsSelected property of a CustomerViewModel object.
-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ItemsControl.AlternationIndex" Value="1" />
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#EEEEEEEE" />
</MultiTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<HeaderedContentControl Header="Model Info" Style="{StaticResource MainHCCStyle}" Grid.Row="0">
<ListView x:Name="lvModelNumbers" Margin="6,2,6,50" DataContext="{StaticResource ProductGroups}"
ItemContainerStyle="{StaticResource ProductItemsStyle}" ItemsSource="{Binding}" >
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="ProductGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Model Number" Width="100" DisplayMemberBinding="{Binding Path=DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
</HeaderedContentControl>
<HeaderedContentControl Header="Model Template" Style="{StaticResource MainHCCStyle}" Grid.Row="1">
<ListBox ItemsSource="{Binding SelectedItem/ProductTemplate, ElementName=lvModelNumbers}" Margin="6,2,6,50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</HeaderedContentControl>
</Grid>
It feels to me like I'm missing the capture of an event to update the listbox, but with my inexperience in MVVM I Can't be sure. I've tried binding to the SelectedItem of the named element but that doesn't work. I can get the listbox to populate if I bind "AllProducts/ProductTemplate", but it just gives me the first indexed values, and doesn't dynamically change when I select another product.
Hopefully that is enough information, and any help would be greatly appreciated. I'm enjoying learning MVVM but it's dificult to wrap my head around.
Thanks,
Try bind the ListBox to the ProductTemplate property of the SelectedItem property of the ListView:
<ListView x:Name="lvModelNumbers" Margin="6,2,6,50"
ItemsSource="{Binding Source={StaticResource ProductGroups}}"
ItemContainerStyle="{StaticResource ProductItemsStyle}">
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="ProductGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Model Number" Width="100" DisplayMemberBinding="{Binding Path=DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
<ListBox ItemsSource="{Binding SelectedItem.ProductTemplate, ElementName=lvModelNumbers}" Margin="6,2,6,50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
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
}
}