I begin with some example code to illustrate my problem. But i'm new at this so it could be i'm missing some basic things. But the example is pretty close to what i would like to do.
XAML (main window):
<StackPanel>
<Button Click="ButtonRemove_Click">Remove</Button>
<Button Click="ButtonAdd_Click">Add</Button>
<TabControl Name="TabFilter" ItemsSource="{Binding Tabs}">
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Path=TextList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
C# code:
public class TestText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Text
{
get
{
return _Text;
}
set
{
_Text = value;
NotifyPropertyChanged();
}
}
private string _Text;
public TestText(string text)
{
Text = text;
}
}
public class Tabs : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<TestText> TextList { get; set; }
public Tabs(ObservableCollection<TestText> list)
{
this.TextList = list;
if (TextList.Count == 0)
TextList.Add(new TestText("Testing"));
}
}
public partial class MainWindow : Window
{
public ObservableCollection<TestText> TextList { get; set; }
public ObservableCollection<Tabs> Tabs { get; set; }
public MainWindow()
{
TextList = new ObservableCollection<TestText>();
Tabs = new ObservableCollection<Tabs>();
Tabs.Add(new Tabs(TextList));
InitializeComponent();
}
private void ButtonAdd_Click(object sender, RoutedEventArgs e)
{
Tabs.Add(new Tabs(TextList));
}
private void ButtonRemove_Click(object sender, RoutedEventArgs e)
{
Tabs.RemoveAt(0);
}
}
When i start the program i get one tab containing "Testing". I then click Remove to delete the tab. When i click Add a new tab is created.
And here is my problem. Since the collection is unchanged i expect, or would like to, that the newly created tab reflects the content in the collection. It should be a tab with the content "Testing", but the tab is empty.
What am i doing wrong?
It should be a tab with the content "Testing", but the tab is empty.
What you are seeing is not that the tab is empty, but instead no tab is selected. Since you removed all existing tabs, no remaining tab can keep the selection. So when the underlying collection is changed again to add another tab, that tab does not get selected automatically. If you click on the tab though to select it, the content is still there. You can actually see this by the way the tab header looks when it’s not selected.
Further note, that all your Tabs instances share the single TestText list you created in your MainWindow. So when you change the content in any tab, you are automatically changing the content in all tabs that exist, or will exist in the future, since all instances always get the same ObservableList instance passed.
Related
In my wpf program i would like to have tabs that would be generated from array or list. I would like to edit files with each tab. Each tab would have corresponding folder with same name, so all tabs should look same (this is why I used DataTemplates), since files in all directories are all generated with same names but their content is different. I have code that generates tabs from array and adds names to tabs.
public class MainWindowViewModel
{
public ObservableCollection<TabViewModel> Tabs { get; set; }
public MainWindowViewModel()
{
this.Tabs = new ObservableCollection<TabViewModel>();
var location = System.AppDomain.CurrentDomain.BaseDirectory.ToString();
string[] folderList = new string[] { "Folder1", "Folder2" };
foreach (string folder in folderList)
{
this.Tabs.Add(new TabViewModel(folder));
string newLocation = location + folder + "\\";//i would like to point tab to this directory
}
}
}
public class TabViewModel
{
public string Name { get; set; }
public TabViewModel(string name)
{
this.Name = name;
}
}
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate >
<TextBox x:Name="fileTextBox"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
But I have 2 problems:
I put textbox in content of a tab and if I create more than 1 tab they all share same content in textbox. I would need to make separate instance for each tab.
When tab is created I would need to assign directory(which i stored in newLocation string) to it and then display file (e.g. sample.txt) in textbox (fileTextBox).
you're experiencing this behavior because, when binding by ItemsSource, all TabControl items are 'optimized' by sharing a panel to render the content (see this question)
I suggest you use the INotifyPropertyChanged interface in your view model and add the file data in the TabViewModel (I added the Location property but you can also add a Content property)
public class MainWindowViewModel
{
public ObservableCollection<TabViewModel> Tabs { get; set; }
public MainWindowViewModel()
{
this.Tabs = new ObservableCollection<TabViewModel>();
var location = System.AppDomain.CurrentDomain.BaseDirectory.ToString();
string[] folderList = new string[] { "Folder1", "Folder2" };
foreach (string folder in folderList)
{
string newLocation = location + folder + "\\";//i would like to point tab to this directory
this.Tabs.Add(new TabViewModel(folder, newLocation));
}
}
public class TabViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
OnPropertyChanged();
}
}
private string _location;
public string Location
{
get { return _location; }
set
{
if (_location == value)
return;
_location = value;
OnPropertyChanged();
}
}
public TabViewModel(string name, string location)
{
this.Name = name;
this.Location = location;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
notice the use of UpdateSourceTrigger to update the viewModel when the textbox content changes
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate >
<TextBox x:Name="fileTextBox" Text="{Binding Location, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</TabControl.ContentTemplate>
I have a ListBox on WP8.1 and want to Bind some items in there. That works all fine, but changing a value on the ItemSource doesn't change anything in the ListBox
<ListBox x:Name="myListBox" Width="Auto" HorizontalAlignment="Stretch" Background="{x:Null}" Foreground="{x:Null}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="PanelTap" Tapped="PanelTap_Tapped">
<Border x:Name="BorderCollapsed">
<StackPanel Margin="105,0,0,0">
<TextBlock Text="{Binding myItem.location, Mode=TwoWay}" />
</StackPanel>
</Border>
</ListBox.ItemTemplate>
</ListBox>
I bind the items via
ObservableCollection<LBItemStruct> AllMyItems = new ObservableCollection<LBItemStruct>();
with
public sealed class LBItemStruct
{
public bool ext { get; set; }
public Container myItem { get; set; }
}
public sealed class Container
{
public string location{ get; set; }
...
}
and when I now want to change the TextBlock Text, nothing happens
private void PanelTap_Tapped(object sender, TappedRoutedEventArgs e)
{
int sel = myListBox.SelectedIndex;
if (sel >= 0)
{
myListBox[sel].myItem.location = "sonst wo";
}
}
The PanelTap_Tapped gets triggered, when I tap the Panel (checked via Debug), but the TextBlock Text does not change
If you want the view to update when a property changes, then you need to have the source object implement INotifyPropertyChaned, and raise the PropertyChanged event:
public sealed class Container : INotifyPropertyChanged
{
public string location
{
get { return _location; }
set { _location = value; RaisePropertyChanged("location"); }
}
private string _location;
...
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null)
handler(new PropertyChangedEventArgs(this, propName));
}
}
Actually the 2 way binding works, but there is a problem.
I have a button on the appbar. Also I have a textbox with TwoWay binding. Now, if I am typing in the textbox, and I remove focus from the textbox (close the keyboard by pressing back key), then the Property to which the textbox text is binded gets updated.
But, if I press the AppBar Button without closing the keyboard, the property does not get updated.
Is there a simple solution to this problem?
All help is greatly appreciated.
Thank You!
Edit:
I tried this.focus on the AppBar button click, but still no luck
Edit 2:
Here is my code-
<StackPanel>
<TextBlock Text="Title" FontSize="{StaticResource PhoneFontSizeMediumLarge}" Margin="15,0,0,0"/>
<TextBox Name="TitleTB" Text="{Binding Title, Mode=TwoWay}" />
<TextBlock Text="Description" FontSize="{StaticResource PhoneFontSizeMediumLarge}" Margin="15,0,0,0"/>
<TextBox Name="DescriptionTB" Text="{Binding Description, Mode=TwoWay}" AcceptsReturn="True" MaxHeight="300" VerticalScrollBarVisibility="Auto" />
</StackPanel>
.cs code-
public CreateTaskPage()
{
InitializeComponent();
M1 = new MyClass { Description = "Description", Title = "title1" };
this.DataContext = M1;
}
private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
//save - I change the text in the textbox from title1 to title123 suppose
// But it still shows title1 if I click the appbar button without closing the keyboard
this.Focus();
MessageBox.Show(M1.Title);
}
Edit 3:
MyClass code-
public class MyClass : INotifyPropertyChanged
{
private string title;
private string description;
public string Title
{
get { return title; }
set
{
title = value;
OnPropertyChanged("Title");
}
}
public string Description
{
get { return description; }
set
{
description = value;
OnPropertyChanged("Description");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Try this:
private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
BindingExpression expression = TitleTB.GetBindingExpression(TextBox.TextProperty);
MessageBox.Show("Before UpdateSource, Test = " + M1.Title);
expression.UpdateSource();
MessageBox.Show("After UpdateSource, Test = " + M1.Title);
}
For more Refrence about binding you can go here Data binding for Windows Phone
Just to make sure, did you properly declare the property of MyClass like below?
class MyClass {
public String Description { get; set; }
public String Title { get; set; }
}
Why dont you shift the focus from textbox to some other control in the click event of the ApplicationBarIconButton.
My WPF Application code generates panels on function call defined in .cs file. There is ItemControl used in code to generates these Panels . I want to Name Textbox defined in this ItemControl and to use this in code. I named it as textEdit1 and used it in code but code generated error that textEdit1 doesn't exist. Can anyone solve my problem? Here Code is:
XAML File:
<dxlc:ScrollBox>
<ItemsControl Name="lstPanels">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="vertical">
<Grid>
<dxe:TextEdit Height="165" Text="{Binding Text,
Mode=TwoWay}" x:Name="textEdit1"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</dxlc:ScrollBox>
.CS FILE
public partial class Window1 : Window
{
string valuu;
public Window1()
{
InitializeComponent();
addPanel("Header1");
addPanel("Header2");
addPanel("Header3");
lstPanels.ItemsSource = panels;
}
public ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
public void addPanel(string buttonId)
{
MyPanel p = new MyPanel { Id = buttonId};
panels.Add(p);
functionb(p);
}
public void functionb(MyPanel obj)
{
valuu = obj.Text;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
foreach (var f in panels.ToList())
{
MessageBox.Show( f.Id + " *** " + f.Text);
}
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged( String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I see that you are using some 3rd party libraries for your TextBox and ScrollBox. If you provide me with the names of the libraries, I could have a look at them as the functionality might be different from what WPF has out-of-the-box.
As for now you have 3 options (I am giving examples for standard TextBox and ItemsControl):
I) You do not have to access the textbox at all.
An easy way around it is described here: StackOverflow post
II) Handling events and references to TextBoxes in the code behind
Add a Loaded event to your TextBox:
<TextBox x:Name="txtText" Width="300" Height="100" Loaded="txtText_Loaded" />
Add a field to your MyPanel class to hold a reference to a TextBox:
public class MyPanel
{
public string Text { get; set; }
public TextBox TextBox { get; set; }
/* the rest ... */
}
Add a counter to your window, next to a list with panels:
protected ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
private int counter = 0;
Handle the Load event of the TextBox:
private void txtText_Loaded(object sender, RoutedEventArgs e)
{
panels[counter].TextBox = (TextBox)sender;
counter++;
}
If you want to access a particular TextBox, do it this way:
MessageBox.Show(panels[i].TextBox.Text);
III) Add additional bindings for FontSize:
Add a FontSize property to your MyPanel class:
private double _fontSize = 10;
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
Bind just added property to the TextBox in your ItemsControl:
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}"
FontSize="{Binding FontSize, Mode=OneWay}" />
Add a slider to the template and bind it to the same property:
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
This way if you change the value on a slider, it will change the value in your MyPanel object bound to the panel. This in turn will change the font size of the textbox.
My whole code I tested it on looks like that:
<ItemsControl x:Name="lstItems" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}" FontSize="{Binding FontSize, Mode=OneWay}" />
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And code behind:
public partial class MainWindow : Window
{
protected ObservableCollection<MyPanel> texts = new ObservableCollection<MyPanel>();
public MainWindow()
{
InitializeComponent();
texts.Add(new MyPanel() { Text = "Test 1" });
texts.Add(new MyPanel() { Text = "Test 2" });
lstItems.ItemsSource = texts;
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
private double _fontSize = 10;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I personally would go with the last solution.
But again, let me know what libraries you are using, and I will have look at them when I have some time. Good luck.
textEdit1 is part of a template that will be instantiated multiple times, so there will be multiple instances of textEdit1. It wouldn't make sense to generate a field for textEdit1 in the class, because it could only refer to one instance the TextEdit control...
I want to view a folder structure in a treeview using a databinding.
The folder class just has a property list of children and a property name.
If something changes it will fire the according event.
This is it:
public class Folder : INotifyPropertyChanged, INotifyCollectionChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public Folder(string name)
{
this.Name = name;
this.ContentFolders = new List<Folder>();
}
public List<Folder> ContentFolders { get; set; }
public void AddFolder(Folder f)
{
this.ContentFolders.Add(f);
if (this.CollectionChanged != null)
{
this.NotifyCollectionChanged(
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, f));
}
this.PropertyChanged(this, new PropertyChangedEventArgs("ContentFolders"));
}
private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
lock (CollectionChanged)
{
if (CollectionChanged != null)
{
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => CollectionChanged(this, e)));
}
}
}
private string name;
public string Name
{
get
{
return this.name;
}
set
{
if (this.name != value)
{
this.name = value;
if (PropertyChanged != null)
{
PropertyChanged(
this, new PropertyChangedEventArgs("Name"));
}
}
}
}
}
This is my GUI, which shows the root folder in a treeview:
<Window x:Class="WpfApplication2.MyWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication2="clr-namespace:WpfApplication2"
Title="MyWindow" Height="300" Width="300" xmlns:u="clr-namespace:UpdateControls.XAML;assembly=UpdateControls.XAML">
<StackPanel>
<StackPanel.Resources>
<HierarchicalDataTemplate DataType="{x:Type WpfApplication2:Folder}"
ItemsSource="{Binding Path=ContentFolders}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</StackPanel.Resources>
<TreeView Name="TreeviewScenario">
<TreeViewItem Header="{Binding Path=RootFolder.Name}"
ItemsSource="{Binding Path=RootFolder.ContentFolders}" />
</TreeView>
<Button Content="Add Folder" Click="Button_Click" />
</StackPanel>
</Window>
The according MyWindow.xaml.cs class has a property Folder and adds some content. It has also a method for the button to add a new folder if it becomes clicked.
public partial class MyWindow : Window
{
public Folder RootFolder { get; set; }
public MyWindow()
{
this.RootFolder = new Folder("root");
this.RootFolder.ContentFolders.Add(new Folder("1"));
this.RootFolder.ContentFolders.Add(new Folder("12"));
this.RootFolder.ContentFolders.Add(new Folder("13"));
this.RootFolder.ContentFolders.Add(new Folder("14"));
this.RootFolder.ContentFolders.Add(new Folder("15"));
Folder aFolder = new Folder("aFolder");
aFolder.ContentFolders.Add(new Folder("2"));
aFolder.ContentFolders.Add(new Folder("21"));
aFolder.ContentFolders.Add(new Folder("22"));
aFolder.ContentFolders.Add(new Folder("23"));
aFolder.ContentFolders.Add(new Folder("24"));
this.RootFolder.ContentFolders.Add(aFolder);
this.DataContext = this;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Folder c = new Folder("a new Folder");
this.RootFolder.AddFolder(c);
}
}
The Gui will be calles by a simple Main method with:
new MyWindow();
If I start, the treeview looks fine, it has all the items, which have been added in the MyWindow.xaml.cs.
But If I click the button, no new items will be shown. If I click the button before I expand the treeview, the new items will be there...
So the view seems not to be updated...
Can anybody see, what I have done wrong?
Change the ContentFolders in your Folder class to an ObservableCollection<> instead of a List<>
public ObservableCollection<Folder> ContentFolders { get; set; }