Why SelectionChanged is called when view is unloaded? - c#

Below is an mcve (to reproduce the problem it has to be datatemplated unloadable view).
xaml:
<Window Content="{Binding}" ... >
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel}">
<ListView ItemsSource="{Binding Items}"
SelectionChanged="ListView_SelectionChanged"
Unloaded="ListView_Unloaded"
MouseRightButtonDown="ListView_MouseRightButtonDown">
</ListView>
</DataTemplate>
</Window.Resources>
</Window>
cs:
public class ViewModel
{
public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>(new[] { "1", "2", "3" });
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e) => Title += "S";
void ListView_Unloaded(object sender, RoutedEventArgs e) => Title += "U";
void ListView_MouseRightButtonDown(object sender, MouseButtonEventArgs e) => DataContext = null;
}
Start program, select item in the list ("S" will be added to title), right click inside list -> "SU" will be added to title.
Question: why SelectionChanged is called when view (ListView) is unloaded??
Right-clicking without selecting anything will add only "U" to the tittle.
Simply closing software (disregarding selection) will not cause SelectionChanged (tested by setting breakpoint).
I will have some logic in SelectionChanged event and I don't want it to run when view is unloaded, it's unexpected behavior what SelectionChanged event is called in this case at all.

Related

Change view lose data - wpf

hi I'm try to develop Datalogger, so i create a menu, to Switch de Options i decided to use DataTemplates and different ViewModels.
Menu
XAML:
<Window.Resources>
<DataTemplate x:Name="GraficoVtemplate" DataType="{x:Type viewmodels:GraficoVM}">
<view:GraficoV DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="ListaVtemplate" DataType="{x:Type viewmodels:ListaVM}">
<view:ListaV DataContext="{Binding }"/>
</DataTemplate>
<ContentControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="5" Content="{Binding}"/>
This is how i change the datacontext;
XAML.CS
private void Novoteste_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = new NovoTesteVM();
}
private void Lista_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = new ListaVM();
}
This is the files that i have, the models are empty and the viewsmodals have some controles.Files
The problem is that when I change the menu, the content of the previous menu is not saved, that is when I select the menu "lista" and fill in a datagrid, and I go to another menu when I select again the menu "lista" the data are lost.I do not know what I need to add, or change so that the data is not lost
Thanks you for the explanation!
Edit 1:
MainWindows.xaml.cs
private void Novoteste_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = NovoTesteVM.NovoTesteViewModel;
}
private void Grafico_btn_Click(object sender, RoutedEventArgs e)
{
DataContext = GraficoVM.Grafico;
}
NovoTesteVM.cs
public class NovoTesteVM
{
private static NovoTesteVM novoTesteViewModel;
public static NovoTesteVM NovoTesteViewModel
{
get
{
novoTesteViewModel = novoTesteViewModel ?? new NovoTesteVM();
return novoTesteViewModel;
}
}
}
Create container properties to keep the wiewmodel for corresponding views. It's loosing data because you are initializing new object on click.
Sample code -
private NovoTesteVM novoTesteViewModel;
public NovoTesteVM NovoTesteViewModel
{
get
{
novoTesteViewModel = novoTesteViewModel ?? new NovoTesteViewModel();
return novoTesteViewModel;
}
}

UWP clicking button within a listview

I'm trying to do some basic UI and binding in UWP to get my head wrapped around it but I'm having trouble accessing a button within a listview item.
I have a Button where on clicking it, it creates a new object which is added to my ObservableCollection which then ends up adding a new item in my ListView
XMAL
<ListView Grid.Row="1" ItemsSource="{x:Bind counts}" x:Name="buttonsView" Margin="5,0" Background="White" Foreground="#FF5059AB">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Counter">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind CountValue, Mode=TwoWay}" FontWeight="Black"/>
<Button Click="Increment_Click" Content="Increment"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C#
public class Counter
{
private int count;
public Counter()
{
count = 0;
}
public int CountValue
{
get
{
return count;
}
set
{
count = value;
}
}
}
public sealed partial class MainPage : Page
{
ObservableCollection<Counter> counts = new ObservableCollection<Counter>();
public MainPage()
{
this.InitializeComponent();
}
private void Increment_Click(object sender, RoutedEventArgs e)
{
// ?
}
private void AddCounter_Click(object sender, RoutedEventArgs e)
{
counts.Add(new Counter());
}
}
That works.
But when I click on the button within the StackPanel, the Increment_Click method is called but I'm not sure what to do at that point. I would like to access the Counter object by getting the index of the ListView item and using that to index into the ObservableCollection.
How do I figure out what the index is of the ListView item that was added?
Instead of an event you should use Command and CommandParameter. You would then bind the Command to a command implemented in Counter and CommandParameter to the item (like {Binding}).
However, you can achieve your goal with Click event as well using DataContext:
private void Increment_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
var counter = (Counter)button.DataContext;
//...
}
Basically DataContext is by default inherited from the parent unless you specify otherwise, so in this case DataContext is the current list item.

WPF UserControl- pre-Load UserControl

I have a MainWindow and 4 UserControls. By switching the DataContext to my UserControls I can have an application with multiple "Pages". In every UserControl I have a webBrowser-Control that Displays an PowerPoint (so -> 4 UC = 4 ppt). The issue I have now is that when I Switch my DataContext (Switch Page) I have to load (call the navigate Method) the whole ppt in my webBrowser again and that takes some Time. How can I fix this?
thanks in advance :))
Adrian
EDIT CODE
MainWindow.xaml
<Window.Resources>
<DataTemplate x:Name="Page1Template" DataType="{x:Type viewmodels:Page1Model}" >
<views:Page1 DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="Page2Template" DataType="{x:Type viewmodels:Page2Model}">
<views:Page2 DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="Page3Template" DataType="{x:Type viewmodels:Page3Model}">
<views:Page3 DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="Page4Template" DataType="{x:Type viewmodels:Page4Model}">
<views:Page4 DataContext="{Binding}"/>
</Window.Resources>
// ...
<ContentControl Content="{Binding}"></ContentControl>
MainWindow.xaml.cs (i call the page Switch like this)
private void menuBtn1_Click(object sender, RoutedEventArgs e)
{
DataContext = new Page1Model();
}
private void menuBtn2_Click(object sender, RoutedEventArgs e)
{
DataContext = new Page2Model();
}
private void menuBtn3_Click(object sender, RoutedEventArgs e)
{
DataContext = new Page3Model();
}
private void menuBtn4_Click(object sender, RoutedEventArgs e)
{
DataContext = new Page4Model();
}
and lets say e.g my UserControl1: (when i call UC1 every time the ppt is opening again, i want just to open it one time):
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
powerPointBrowser1.Navigate("somePPTfile.pptx");
powerPointBrowser1.LoadCompleted += powerPointBrowser1_LoadCompleted;
}
i hope i made it clear :S
In Mainwindow.xaml Place the view in stackpanel to make visiblity hide show like this.
<DataTemplate x:Name="Page1Template" DataType="{x:Type viewmodels:Page1Model}">
<StackPanel Visibility="{Binding Page1}">
<views:Page1 DataContext="{Binding}"/>
</StackPanel>
</DataTemplate>
In MainWindow.xaml.cs you have to set a property for visibility like this.
private Visibility page1;
public Visibility Page1
{
get { return page1; }
set { page1 = value; }
}
Then initialize the DataContext of each view in your MainWindowLoad function, so that it will be preloaded. After that you can set the visibility for the each view in your each menu click function like this:
Page1 = Visibility.Visible; or Page1 = Visibility.Collapsed;
I hope it will works.

How to update the content of wpf listbox from different window

I've two windows: Main Window, Log Window. How can I update the listbox in the Log Window when some action is happened in the Main Window (e.g. button is clicked)?
Below is the code for listbox in Log Window:
<ListBox x:Name="DebugLogLb" BorderBrush="{x:Null}">
<TextBlock x:Name="DebugLogTb" Text="{Binding LogText}" Background="{x:Null}" />
</ListBox>
When the button in the Main Window is clicked, it will update the listbox. I tried with the code below but it doesn't work.
private void Btn1_Click(object sender, RoutedEventArgs e)
{
var log = new LogWindow();
log.DebugLogLb.Items.Add(new { LogText = "Button 1 is clicked" });
}
I'm able to update the listbox if I put everything in the same window, but I failed to do so with two windows.
My expected output would be like:
Even if both windows are opened, when the buttons in the Main Window are clicked, it will directly update in the Log Window as well.
Thanks for any helps in advanced.
It's hard to tell where you are going wrong without seeing more of the code. This is an example that works. It creates a new LogWindow in the MainWindow ctor and sets the DataContext. When the button is clicked the handler calls show on the window. The ListBox's itemssource property is bound to an ObservableCollection of strings. So any adds/removes are automatically updated on the UI.
LogWindows xaml
<Window x:Class="WpfApplication7.LogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LogWindow" Height="300" Width="300">
<Grid>
<ListBox x:Name="DebugLogLb" BorderBrush="{x:Null}" ItemsSource="{Binding LogText}" />
</Grid>
MainWindow code-behind
public partial class MainWindow : Window
{
LogWindow _logWindow;
public MainWindow()
{
InitializeComponent();
LogText = new ObservableCollection<string>();
_logWindow = new LogWindow();
_logWindow.DataContext = this;
_logWindow.Closed += _logWindow_Closed;
}
private void _logWindow_Closed(object sender, EventArgs e)
{
_logWindow = new LogWindow();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_logWindow.Show();
LogText.Add("Button1 Clicked");
}
public ObservableCollection<string> LogText { get; set; }
}

WPF Listbox SelectionChanged

I currently have an Entity that has a collection property on it. I want to know if why would the SelectionChanged only fire once and it won't trigger the SelectionChanged again once I try to select the item that was previously selected.
MainWindowViewModel
public MainWindowViewModel()
{
var a = new List<Test>();
a.Add(new Test() { Name = "Leo", Test1 = new List<Test1> { new Test1() { Content = "aaa"} } });
a.Add(new Test() { Name = "2", Test1 = new List<Test1> { new Test1() { Content = "bbb"} } });
a.Add(new Test() { Name = "Le33o", Test1 = new List<Test1> { new Test1() { Content = "ccc"} } });
A = a;
}
private List<Test> _a;
public List<Test> A
{
get { return _a; }
set { _a = value; OnPropertyChanged("A");}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
My Mainwindow
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
private void Test(object sender, SelectionChangedEventArgs e)
{
}
My listbox structure
public class Test
{
public List<Test1> Test1 { get; set; }
public string Name
{
get;set;
}
}
public class Test1
{
public string Content { get; set; }
}
I select the first object, the event fires, I select the second object, the event fires, I select the first object, the event doesn't fire, I select third object, the event fires. It seems like it only triggers and calls the event once.
My XAML Code:
<ItemsControl x:Name="Lists" ItemsSource="{Binding A}" Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold"
Style="{StaticResource DefaultTextBlockStyle}" />
<ListBox SelectionChanged="Test" ItemsSource="{Binding Test1}"
Margin="5,0,0,0" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Content}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The test method is just an empty method I just want to hit the breakpoint every time I change.
private void Test(object sender, SelectionChangedEventArgs e)
{
}
Update 1: I tried to reproduce this in a simple WPF app, it seems that the ListBoxItem is getting IsEnabled to false but I snooped it and all the controls are enabled. It just getting a grey background that looks like disabled. Will try to investigate further.
Update 2: It seems that the ListBoxItem IsSelected property is not being unset when you change an item.
To answer your question ...
I want to know if why would the SelectionChanged only fire once and it won't trigger the SelectionChanged again once I try to select the
item that was previously selected.
... in a learning by doing way
open a new WPF Project add 2 Listboxes create ONE SelectionChanged event for both and add some items to each Listbox
let's mention it look's now like this
<ListBox Height="100" Name="listBox1" Width="120" SelectionChanged="listBox_SelectionChanged"/>
<ListBox Height="100" Name="listBox2" Width="120" SelectionChanged="listBox_SelectionChanged"/>
.
var list = new List<string>();
list.Add("Element1");
list.Add("Element2");
list.Add("Element3");
list.Add("Element4");
listBox1.ItemsSource = list;
listBox2.ItemsSource = list;
If you now select Element1 in listBox1 your listBox_SelectionChanged get triggert after that select Element2 in your listBox2 so your listBox_SelectionChanged get's triggert again.
If you take a closer look at your listBox1 you will see that the Background behind your Element1 is gray which means it is selected, but with out focuse. If you select now the Element1 in your listBox1 again the listBox_SelectionChanged doesn't get triggert because the selection does't change only the Focuse does.
That's the exact same "problem" is in your code because your DataTemplate does the same think we did as we added our 2 Listboxes just automatically
as simple and dirt workaround you could use the following code
private object seletedItem;
private ListBox ItemsHost;
private void Test(object sender, SelectionChangedEventArgs e)
{
var buff = sender as ListBox;
if (seletedItem != null)
if (ItemsHost != buff)
ItemsHost.SelectedItem = null;
ItemsHost = buff;
if (e.AddedItems.Count > 0)
seletedItem = e.AddedItems[0];
}
The simple solution I found out is to make selectedItem as null in the event handler.
private void tempList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Card selectedOffer = (TempList.SelectedItem as Card);
if (selectedOffer != null)
{
MessageBox.Show(selectedOffer._id);
}
ListBoxNeeded.SelectedItem = null;
}
Selecting the same item is not a SelectionChanged event. The selection did not change.
The problem statement is not clear.
Break it down. This works for me. If I select any item a second time, 3rd, 4th time the event fires.
OP asserted it does not work if it is a List in a List. Still works for me.
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
public List<ListList> ListList1
{
get { return new List<ListList>{new ListList("name1", new List<string> { "one", "two", "three" })}; }
}
private void Test(object sender, SelectionChangedEventArgs e)
{
ListBox lb = (ListBox)sender;
System.Diagnostics.Debug.WriteLine(lb.SelectedItem.ToString());
}
public class ListList
{
public string Name { get; set; }
public List<string> Values { get; set; }
public ListList(string name, List<string> values) { Name = name; Values = values; }
}
<ListBox ItemsSource="{Binding Path=ListList1}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBox SelectionChanged="Test" ItemsSource="{Binding Path=Values}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories

Resources