I know this has been asked a few times but none of the answers work for me.
I've got this list of elements in a TreeView which when clicked show their parameters in a panel (with texboxes, labels, combos and other components). I also have a button that allows copying data from one element chosen in a combo to the one currently shown on the data panel ("copy data from...").
All the data in the panel is bound to a class and its properties, and the treeview is bound to a collection of those objects.
The problem is, when I press the copy button (i.e. copying data from object A to current object) the changes are not reflected. Yet, if I change the element to be displayed (clicking another object in the TreeView) then go back to the changed object, the changes are there. So it is in fact changing the actual data, but not refreshing de data bindings.
Strangely enough, If I press the Copy button twice, changes are indeed reflected.
Which may be the cause of this, and how would I solve it?
This is the XAML example of a control that should be updated (I only post one because there are lots, and none of them is working), the copy button and the treeview:
Copiar datos del protocolo:
Copiar
<TreeView SelectedItemChanged="TvProtocolosSelectedItemChanged" Margin="10,5" Name="tvProtocolos" HorizontalAlignment="Stretch" VerticalAlignment="Top" Width="200" Height="292" MinWidth="0">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsEnabled" Value="{Binding Path=Activo}"/>
<Setter Property="IsExpanded" Value="False"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<Image Source="imagenes\file_icon.gif" Margin="0,0,5,0" />
<TextBlock Text="{Binding Path=Name}" ></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And this is the code-behind file:
public partial class NewXP2 : Window
{
private const int nProtocolos = 100;
private Experiencia2 _exp2Class = new Experiencia2(nProtocolos);
private readonly TreeView _tvProtocolos;
public NewXP2()
{
InitializeComponent();
_tvProtocolos.ItemsSource = _expClass.Protocolos;
xpControlsPanel.DataContext = _exp2Class.GetProtocolo(0);
for(int i=0;i<nProtocolos;i++)
copyFromCombo.Items.Add("Protocolo " + (i+1));
copyFromCombo.SelectedIndex = 0;
}
private void CopyfromButtonClick(object sender, RoutedEventArgs e)
{
int protIndex = copyFromCombo.SelectedIndex;
int indiceProtocolo = 0;
if (_tvProtocolos == null)
return;
var g = _tvProtocolos.SelectedItem as Composite;
_listaData = GetData();
_tvProtocolos.ItemsSource = _listaData;
TreeViewItem tvi;
if (g != null)
{
tvi = _tvProtocolos.ItemContainerGenerator.ContainerFromIndex(g.Indice) as TreeViewItem;
if (tvi != null)
{
tvi.IsSelected = true;
}
indiceProtocolo = g.Indice;
}
_exp2Class.SetProtocolo(indiceProtocolo, _exp2Class.Protocolos[protIndex]);
}
And this is the class bound to the panel (with the textbox)
public class ProtocoloExp2: ISerializable, IDataErrorInfo
{
public ProtocoloExp2(int idx)
{
IndiceProtocolo = idx;
IndiceVisual = idx + 1;
}
public float TimeToShowTarget { get; set; }
(...)
}
Check "How to: Implement the INotifyPropertyChanged Interface" out. And consider using ObservableCollection for your lists. Implementation of INotifyPropertyChanged interface is a requirement for wpf controls view models it basically allows views to refresh themselves when data changes.
Related
I have a ListBox, where the list element has a ComboBox, a TextBox and a slider. Depending on the selction of the ComboBox either the TextBox or the slider should be visible.
<ListBox Name="lstPWM" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<!-- more definitions -->
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding GeberVisible}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding WertVisible}" Value="{Binding Wert, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code behind is:
public partial class MainWindow : Window
{
public ObservableCollection<PWMKanal> PWM_col { get; set; } = new();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
lstPWM.ItemsSource = PWM_col;
foreach (var item in Board.PWM) PWM_col.Add(item); //Board.PWM is the data source.
}
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox box = sender as ComboBox; // Finding the line in the ListBox.
PWMKanal PWM = box.DataContext as PWMKanal;
int z = PWM_col.IndexOf(PWM);
Board.PWM[z].Gebertyp = (QuellePWM)box.SelectedValue;
if (Board.PWM[z].Gebertyp == QuellePWM.Sender)
{
PWM_col[z].GeberVisible = Visibility.Visible; // I thought that i may change the
PWM_col[z].WertVisible = Visibility.Hidden; // ObservableColelction directly
} // but the display is not updated.
else // In Debug mode i see, that PWM_coll
{ // is changed as expected, but no effect
PWM_col[z].GeberVisible = Visibility.Hidden; // on the GUI.
PWM_col[z].WertVisible = Visibility.Visible;
}
if (PWM_col.Count != 0) // this code is intended to update the GUI, but every time
{ // a new item is added the Selection Change fires again
PWM_col.Clear(); // and i get a stack overflow in an endless loop.
foreach (var item in Board.PWM) PWM_col.Add(item);
}
}
}
The comments describe my approaches and problems:
I change the selected element of the ObservableCollection directly, but this has no effect on GUI. At least tho code doesn't crash.
I clear the list ObservableCollection PWM_col, but then i get an infinite loop: every time an element is added to the list the SelectionChange event fires, calling the routin again. Result is stack overflow.
Now my questions to my approaches:
Is it possible to change an element of an ObservableCollection directly by code, and the display is automatically refreshed?
Is it possible to somehow catch the SelectionChanged event before the handler is executed? Or is it possible to temporary dissable the event?
Any other idear?
Thank you for your help!
CollectionChanged does notify, that collection itself, not the
single items, is changed. Therefore to see the changes item's
property need to implement INotifyPropertyChanged. Also remove Mode=OneTime
You can of course set the flag, that PWMTyp_SelectionChanged is
running:
private bool selChangedIsRunning = false;
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(selChangedIsRunning) return;
selChangedIsRunning = true;
// do stuff ....
selChangedIsRunning = false;
}
Other idea is - don't use the SelectionChange event, but do bind
Slider.Visibility and TextBox.Visibility to the
ComboBox.SelectedValue and use value converter to define the
Visibilty, also you can use the ConverterParameter.
<ComboBox x:Name="CmbPWMTyp" ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=TBX}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=SLDR}" Value="{Binding Wert, Mode=TwoWay}"/>
This link can be also very helpful for you: Difference between SelectedItem SelectedValue and SelectedValuePath
I have defined a label with name and I'm trying to access it but no luck. Let me explain my problem with my code.
<ListView Name="gridListView" ItemsSource="{Binding... }">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel Orientation="Vertical">
<Label x:Name="vLabel" Content="{Binding VCValue}"/>
<ListView Name="checkBoxListView" ItemsSource="{Binding CList}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Margin="5" Click="CheckBox_Click" IsChecked="{Binding SelectedValue, Mode=TwoWay}" Content="{Binding Current, Mode=OneWay }"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the above code, I have two listview, gridListView and checkBoxListView. Here, I want to access the label vLabel which is inside the datatemplate of gridListView when the one of the value in checkbox(which is inside checkBoxListView) is clicked.
I understand it can't be accessed directly as its within datatemplate so i tried below code as suggested in other forums but gridListView.SelectedIndex is always -1 so i know i'm not doing the right thing. When I just hardcoded gridListView.SelectedIndex to index 0, 1 or 2 its giving me the right value of vLabel so the below code will work if gridListView.SelectedIndex is correct.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox chk =(CheckBox)sender;
int index = gridListView.Items.IndexOf(chk.DataContext);
ListViewItem item = gridListView.ItemContainerGenerator.ContainerFromIndex(gridListView.SelectedIndex) as ListViewItem;
if (item!=null)
{
//get the item's template parent
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = gridListView.ItemTemplate;
if (dataTemplate != null && templateParent != null)
{
var lab = dataTemplate.FindName("vLabel", templateParent) as Label;
var v = lab.Content;
}
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
//I can post this function if need be
....
}
Appreciate any help that will help me access vLabel.
Thanks in advance
you are setting focusable to false, so you cant select the item by clicking. also the checkbox-checked happens before the selection, even if you were to set focusable to true, so selected index would still be -1.
you can find your label simply like this:
public Label FindLabel(CheckBox checkBox)
{
var listView = VisualTreeHelper.GetParent(checkBox);
while (listView.GetType() != typeof(ListView))
{
listView = VisualTreeHelper.GetParent(listView);
}
return (listView as FrameworkElement).FindName("vLabel") as Label;
}
but i suggest you tell us what you want to achieve, because this doesnt seem like a clean solution.
can you perhaps do this on your StackPanel:
<StackPanel CheckBox.Checked="CheckBox_Click" Orientation="Vertical">
and then access your two desired properties as following:
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var outerItem = (sender as FrameworkElement).DataContext;
var innerItem = (e.OriginalSource as FrameworkElement).DataContext;
}
and then do whatever you want to do?
Thank you for all your guidance. I did get hint from Milan's code to achieve solution for my issue. Here, I'm trying to get reference parent of listviewItem(i.e stackpanel) and then access its child. In my case, stack panels child at index 0 is Label and at index 1 is ListView. So then I go through visualtreehelper to get reference to its child at index 0 which is what i need access to. So here is the code snippet.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
//to access parent of the checkbox
ListViewItem listViewItem =
GetVisualAncestor<ListViewItem>((DependencyObject)sender);
//to access parent of the listViewItem(which is parent of checkbox)
StackPanel stackPanel =
GetVisualAncestor<StackPanel>((DependencyObject)listViewItem);
int childCount = VisualTreeHelper.GetChildrenCount(stackPanel);
//to access child of stackpanel to access the current vLabel
var vValue = VisualTreeHelper.GetChild(stackPanel, 0) as Label;
}
private static T GetVisualAncestor<T>(DependencyObject o)where T : DependecyObject
{
...
}
I was wonder if any one knows how to change the visibility of a listbox within a DataTemplate when a sibling is clicked. The DataTemplate is being used on a listbox. The following is an example of the xaml I'm using:
<DataTemplate x:Key="Template1">
<StackPanel Margin="110,0,0,0">
<TextBlock Text="{Binding Name}" />
<TextBlock Name="ShowHide" Text="Hide" Tap="ShowHide_Tap" />
<ListBox Name="Listbox1" ItemsSource="{Binding SecondList}" Visibility="Visible" ItemTemplate="{StaticResource Template2}"/>
</StackPanel>
</DataTemplate>
The following is my attempt but I can't use the FindName
private void ShowHide_Click(object sender, System.Windows.Input.GestureEventArgs e)
{
var item = sender as TextBlock;
ListBox Listbox = null;
if (item != null)
{
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = templateParent.ContentTemplate;
if (dataTemplate != null && templateParent != null)
{
Listbox = templateParent.FindName("Listbox1") as ListBox;
}
if (Listbox != null)
{
MessageBox.Show(String.Format("ERROR!"));
}
else
Listbox.Visibility = Visibility.Collapsed;
}
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
FrameworkElement child = null;
for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceElement); i++)
{
child = VisualTreeHelper.GetChild(referenceElement, i) as FrameworkElement;
System.Diagnostics.Debug.WriteLine(child);
if (child != null && child.GetType() == typeof(T))
{ break; }
else if (child != null)
{
child = GetFrameworkElementByName<T>(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
}
}
return child as T;
}
If any one has any insights they would be much appreciated,
Thanks.
It so happens that the Blend SDK provides functionality for this -- you can even use XAML only, no code behind. Just use an EventTrigger (on the "Tap" event) along with a ChangePropertyAction. Here's how it looks:
<TextBlock Name="ShowHide" Text="Hide" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<ei:ChangePropertyAction TargetName="Listbox1"
PropertyName="Visibility" Value="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<ListBox Name="Listbox1" ItemsSource="{Binding SecondList}" Visibility="Visible" />
Note that this requires you to add references to the following Extensions:
System.Windows.Interactivity
Microsoft.Expression.Interactions
Reference them in XAML with the namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Welcome to StackOverflow!
Generally speaking this is not the way to use WPF, especially if you're using DataTemplates. The purpose of the view in WPF is to display the view model and fire off user events, nothing more. By changing the Data Template at run-time you're effectively storing the state of the view inside the view itself. This runs totally against the grain of how WPF was designed to be used.
To do this properly your view model (i.e. the class with the SecondList property) should have an extra property called something like ListBoxVisibility and you would bind your listbox's Visibility member to that. An even cleaner method is to use a bool and then use a converter in the view to convert it from type bool to type Visibility. Either way the view model should also have a property of type ICommand (e.g. OnButtonPressedCommand) that the button invokes when the user presses it. The handler for OnButtonPressedCommand, which should also be in the view model, then sets ListBoxVisible or whatever to the value you want which then propagates through to the list box. Doing things this way keeps good separation of concerns and means the view model can be created and its visibility-changing behavior unit tested independently without having to create the view itself.
I'm trying to create a tree header within a TreeView, but nothing will display. In my program, I'm creating a series of ellipse objects on a canvas that have names. What I'm trying to do is this:
When the ellipse object is created, create a header within the TreeView using the ellipse' names.
Since the first ellipse has a name of "Circle01," this is what I'm trying to display in the TreeView when the Left Mouse button is released. But when I do so, nothing happens.
// Ellipse object is created when Left Mouse button is release.
private void _canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (mouseLBDown)
{
mouseLBDown = false;
if (isCreatingEllipse)
{
_debugLabel.Content = "Done.";
isCreatingEllipse = false;
loadToTree = true;
}
}
}
// Suppose to load name of Ellipse as a header, but doesn't.
private void _circleTree_Loaded(object sender, RoutedEventArgs e)
{
if (loadToTree)
{
TreeViewItem item = new TreeViewItem();
item.Header = circle[0].circleName;
_circleTree.Items.Add(item);
loadToTree = false;
}
}
XAML:
<TreeView Name="_boneTree" Background="#404040" Foreground="#E0E0E0"
HorizontalAlignment="Left" Height="422" Margin="970,28,0,0"
VerticalAlignment="Top" Width="212" Loaded="_circleTree_Loaded"/>
You should not create your own TreeViewItems, rather set the TreeView's ItemsSource and let WPF build the TreeViewItems for you. Also it is easier to do this in XAML:
<TreeView Name="_boneTree" Background="#404040" Foreground="#E0E0E0"
HorizontalAlignment="Left" Height="422" Margin="970,28,0,0"
VerticalAlignment="Top" Width="212"
ItemsSource="{Binding Circles}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="Header" Value="{Binding circleName}"/>
</Style>
</TreeView.Resources>
</TreeView>
Where Circles is a property on your DataContext which by default will be your codebehind:
public ObservableCollection<Circle> Circles { get; set; }
Then whenever a Circle is added or removed it will automatically show up on the UI!
Overview
I have an application, that displays data from an observable collection. The observable collection is (in this debugging setting) created and instanciated only once, then the values stay the same.
The main view of the application contains a ListBox that is bound to said observable collection:
<ListBox x:Name="MainListBox" ItemsSource="{Binding Items}" SelectionChanged="MainListBox_SelectionChanged" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel MinWidth="456" MaxWidth="456" Background="White" Margin="0,0,0,17">
<sparklrControls:SparklrText Post="{Binding Path=.}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<!-- Workaround used to stretch the child elements to the full width -> HorizontalContentAlignment won't work for some reason... -->
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The child items are bound to a UserControl. This UserControl implements a DependancyProperty which the child elements are bound to:
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(object), new PropertyMetadata(textPropertyChanged));
private static void postPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SparklrText control = d as SparklrText;
control.Post = (ItemViewModel)e.NewValue;
}
Binding to the post property configures other variables via the getter of the Post property
public ItemViewModel Post
{
get
{
return post;
}
set
{
if (post != value)
{
this.ImageLocation = value.ImageUrl;
this.Username = value.From;
this.Comments = value.CommentCount;
this.Likes = value.LikesCount;
this.Text = value.Message;
post = value;
}
}
}
This setter configures other which in turn set up elements in the user control. Nothing in the user control is bound, the few updates are done with direct access to the respective Content/Text properties. ImageLocation performs an asynchronous download of an image with
private void loadImage(string value)
{
WebClient wc = new WebClient();
wc.OpenReadCompleted += (sender, e) =>
{
image = new BitmapImage();
image.SetSource(e.Result);
MessageImage.Source = image;
};
wc.OpenReadAsync(new Uri(value));
}
Issue
When I scroll down in the list box and back up, the setter of Post is executed when the owning element comes back into view. The problem: value is a different instance of ItemViewModel. The ListBox ItemsSource is not accessed in any way from outside the class. When scrolling back up, it seems like the wrong Items are bound to the elements, resulting in distorted designs. Are there any issues with the Binding that cause this?
The issue was caused by the ListBox. Elements that are scroll out of view are recycled and appended on the other side. In the code above, a asynchronous operation did not check if the result was still valid, causing wrong display data.