How to Programatically Add Images to WPF Tab Control Item - c#

I am creating TabItems programmatically without any issue by using the following code:
var tabItem = new TabItem();
tabItem.Header = "My Tab Header";
tabItem.Content = new UserControl1();
MainTabControl.Items.Add(tabItem);
Now when a tab item added to tab control i also want to add image button at the same time with the creation of TabItem aligned at right side. How can i achieve this? thanks in advance.
EDIT:
I have tried a lot and still did not get an idea. following is my tabcontrol in xaml and ObservableCollection. When i run project tabs shows successfully but i don't know how to add images in it because in my tab control in xaml, it does not have TabItems markup and they are displaying automatically when running project. Please view my sample code and transform into my desired result. I wana conclude and close this issue, I Really thanks and appreciate help.
Following is xaml:
<TabControl ItemsSource="{Binding TabItems, Mode=TwoWay}" DisplayMemberPath="Content" HorizontalAlignment="Left" Height="73" Margin="10,25,0,0" VerticalAlignment="Top" Width="312"/>
Following is viewmodel
namespace WpfApplication1.ViewModels
{
public class VMTabControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyname)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyname));
}
[![enter image description here][1]][1]}
public VMTabControl()
{
TabItems = new ObservableCollection<clsTabs>(GetList().ToList());
}
private ObservableCollection<clsTabs> _tabItems;
public ObservableCollection<clsTabs> TabItems
{
get { return _tabItems; }
set
{
_tabItems = value;
OnPropertyChanged("TabItems");
}
}
public List<clsTabs> GetList()
{
List<clsTabs> tablist = new List<clsTabs>();
tablist.Add(new clsTabs { Content = "First", ImgPath = "path" });
tablist.Add(new clsTabs { Content = "Second", ImgPath = "path" });
return tablist;
}
}
}

In code
TabItem.Header is not limited to displaying strings - you can set any UI control on it. For example:
tabItem.Header = new Button { Content = "Click me" };
To display both text and a close button, you could use a horizontal stack panel that contains a text block and a button.
In XAML
However, UI layouts are most often written in XAML. The following XAML assumes you have an Items property in your view model, which is a collection of items. These items have a TabHeaderName and TabImagePath property. The view model should also have a RemoveTabCommand property, which is an ICommand that takes a single argument (the tab item to be removed):
<TabControl ItemsSource="{Binding Items}">
<!-- // If you only need to display a single property, you can use DisplayMemberPath.
// If you need something more fancy (such as a text-block and button next to each other),
// you'll have to provide a tab header template instead: -->
<TabControl.ItemTemplate>
<DataTemplate>
<!-- // Tab item header template (this is how each tab header will look): -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TabHeaderName}" />
<Button Content="X"
Command="{Binding DataContext.RemoveTabCommand, RelativeSource={RelativeSource AncestorType=TabControl}}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<!-- // Tab item content template (this is how each tab content will look): -->
<Image Source="{Binding TabImagePath}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
If Items is an observable collection, simply adding an item to it will automatically add a tab item for it. Likewise, removing an item will remove its tab item.

Try this:
var tabItem = new TabItem();
var stack = new StackPanel();
var t = new TextBlock();
t.Text = "My Tab Header";
var i = new Image();
//i.Source = ...
stack.Children.Add(t);
stack.Children.Add(i);
tabItem.Header = stack;
tabItem.Content = new StackPanel();
tab.Items.Add(tabItem);

Related

Why does a dynamic TabControl seem to be unable to switch between the tabs?

I'm using a dynamic TabControl in a page:
<TabControl x:Name="DynamicTabControl"
ItemsSource="{Binding TabsCollection, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TabView}}}"
SelectionChanged="tabDynamic_SelectionChanged">
<TabControl.ItemTemplate>
<DataTemplate x:Name="TabHeader" DataType="TabItem">
<DockPanel Width="30">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=TabItem }, Path=Header}" />
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate >
<local:TabItem/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
public ObservableCollection<TabItem> TabsCollection {get;set;}
private TabItem _tabAdd;
#region Initialization
public TabView()
{
InitializeComponent();
TabsCollection = new ObservableCollection<TabItem>();
TabsCollection.Add(new TabItem { Header = "Tab 1"});
// add a tabItem with + in header
_tabAdd = new TabItem
{
Header = "+"
};
TabsCollection.Add(_tabAdd);
DynamicTabControl.DataContext = TabsCollection;
DynamicTabControl.SelectedIndex=0;
}
#endregion
private TabItem AddSubsequentTabItem()
{
int count = TabsCollection.Count;
TabItem tab = new TabItem();
tab.Header = string.Format("Tab {0}", count);
tab.Name = string.Format("tab{0}", count);
TabsCollection.Insert(count - 1, tab);
return tab;
}
private void tabDynamic_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabItem tab = DynamicTabControl.SelectedItem as TabItem;
if (tab == null) return;
if (tab.Equals(_tabAdd))
{
// clear tab control binding
DynamicTabControl.DataContext = null;
TabItem newTab = this.AddSubsequentTabItem();
// bind tab control
DynamicTabControl.DataContext = TabsCollection;
// select newly added tab item
DynamicTabControl.SelectedItem = newTab;
}
else
Dispatcher.BeginInvoke((Action)(() => DynamicTabControl.SelectedIndex = DynamicTabControl.SelectedIndex));
}
But, I'm unable to get the tab control to switch between my TabItems.
I'm using an amalgamation of the dynamic tab control from https://www.codeproject.com/Articles/493538/Add-Remove-Tabs-Dynamically-in-WPF , with modifications to create the GUI objects from within xaml.
My title pretty much says it all: Why is my TabControl not switching tabs? Even besides fixing the problem, I'd like to know if subscribing a method to the "selectionChanged" event unsubscribes the native tab switching.
Here is the barebones example project of the problem

WPF Add trigger to an existing style from code behind

I have a TabControl with some tabs declared in XAML. I want to add new tabs and bind their IsEnabled properties to some properties of their content:
for (int i = 0; i < context.Pictures.Count; ++i)
{
var tabItem = new TabItem();
var title = "Some title"
tabItem.Header = title;
var image = new Image();
Binding sourceBinding = new Binding(nameof(context.Pictures) + $"[{i}]");
sourceBinding.Source = context;
image.SetBinding(Image.SourceProperty, sourceBinding);
image.Width = 800;
image.Height = 600;
DataTrigger isEnabledTrigger = new DataTrigger() { Binding = sourceBinding, Value = null };
isEnabledTrigger.Setters.Add(new Setter(TabItem.IsEnabledProperty, false));
tabItem.Content = image;
tabControl.Items.Add(tabItem);
}
I want to disable tab if the picture inside is null (apply isEnabledTrigger). Problem here is that style of tabItem is derived from tabControl containing it, so I cannot just create a style with my trigger and apply it to TabItem. Sure, I could just copy original style and hardcode it, but I don't think it's a good way to solve my problem.
So, to solve my problem I have two ideas:
Create a shallow copy of existing style, add trigger and apply it
Load original style from XAML, add trigger and apply it (may be difficult, since it lies in another project)
Is there more rational way to bind TabControls IsEnabled to contained Images value?
Don't add TabItem directly. Use data models. This is recommended approach for all item controls. Then define a DataTemplate for the data model(s) and assign it to TabControl.ContentTemplate.
Use the TabControl.ItemTemplate to layout the header.
Defining a Style for the TabControl.ItemContainerStyle allows you to set up the required triggers quite easily. Doing layout using C# is never a good idea. Always use XAML.
See: Data binding overview in WPF, Data Templating Overview
The minimal model class should look like this:
PictureModel.cs
// All binding source models must implement INotifyPropertyChanged
public class PictureModel : INotifyPropertyChanged
{
private string title;
public string Title
{
get => this.title;
set
{
this.title = value;
OnPropertyChanged();
}
}
private string source;
public string Source
{
get => this.source;
set
{
this.source = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<PictureModel> Pictures { get; }
private void CreateTabItems(Context context)
{
foreach (string imageSource in context.Pictures)
{
var pictureModel = new PictureModel()
{
Title = "Some Title",
Source = imageSource
};
this.Pictures.Add(pictureModel);
}
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<!-- Layout the tab content -->
<TabControl ItemsSource="{Binding Pictures}">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:PictureModel}">
<Image Source="{Binding Source}" />
</DataTemplate>
</TabControl.ContentTemplate>
<!-- Layout the tab header -->
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:PictureModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Setup triggers. The DataContext is the current data model -->
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Source}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Window>
You should base your Style on the current Style:
Style style = new Style(typeof(TabItem))
{
BasedOn = FindResource(typeof(TabItem)) as Style
};
DataTrigger isEnabledTrigger = new DataTrigger() { Binding = sourceBinding, Value = null };
isEnabledTrigger.Setters.Add(new Setter(TabItem.IsEnabledProperty, false));
style.Triggers.Add(isEnabledTrigger);
tabItem.Style = style;
Or
Style style = new Style(typeof(TabItem))
{
BasedOn = tabControl.ItemContainerStyle
};
...
...depending on how your current Style is applied.
This is how you would extend an existing Style with your DataTrigger and this is a good way of solving this.

ListBox filled with binding doesn't select item on click

I'm trying to use a ListBox to choose an entry and then display a picture belonging to this selected entry. But just at the beginning I got my first problem: filling the ListBox with binding is working, but if I click on one line in my running program, it doesn't select the line. I can just see the highlighted hover effect, but not select a line. Any ideas what my mistake could be?
This is my XAML:
<ListBox x:Name="entrySelection" ItemsSource="{Binding Path=entryItems}" HorizontalAlignment="Left" Height="335" Margin="428,349,0,0" VerticalAlignment="Top" Width="540" FontSize="24"/>
And in MainWindow.xaml.cs I'm filling the ListBox with entries:
private void fillEntrySelectionListBox()
{
//Fill listBox with entries for active user
DataContext = this;
entryItems = new ObservableCollection<ComboBoxItem>();
foreach (HistoryEntry h in activeUser.History)
{
var cbItem = new ComboBoxItem();
cbItem.Content = h.toString();
entryItems.Add(cbItem);
}
this.entrySelection.ItemsSource = entryItems;
labelEntrySelection.Text = "Einträge für: " + activeUser.Id;
//show image matching the selected entry
if (activeUser.History != null)
{
int index = entrySelection.SelectedIndex;
if (index != -1 && index < activeUser.History.Count)
{
this.entryImage.Source = activeUser.History[index].Image;
}
}
}
So I can see my ListBox correctly filled, but not select anything - so I can't go on with loading the picture matching the selected entry.
I'm still quite new to programming, so any help would be great :)
EDIT: If someone takes a look at this thread later: here's the - quite obvious -solution
XAML now looks like this
<ListBox x:Name="entrySelection" ItemsSource="{Binding Path=entryItems}" HorizontalAlignment="Left" Height="335" Margin="428,349,0,0" VerticalAlignment="Top" Width="540" FontFamily="Siemens sans" FontSize="24">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to fill it:
//Fill listbox with entries for selected user
DataContext = this;
entryItems = new ObservableCollection<DataItem>();
foreach (HistoryEntry h in selectedUser.History)
{
var lbItem = new DataItem(h.toString());
entryItems.Add(lbItem);
}
this.entrySelection.ItemsSource = entryItems;
labelEntrySelection.Text = "Einträge für: " + selectedUser.Id;
And new Class DataItem:
class DataItem
{
private String text;
public DataItem(String s)
{
text = s;
}
public String Text
{
get
{
return text;
}
}
}
You are filling it with ComboBoxItem, which is not relevant to the ListBox, and also wrong by definition.
You need to have the ObservableCollection filled with data items.
Meaning, make a class that contains the data you want to store, and the ListBox will generate a ListBoxItem automatically per data item.
http://www.wpf-tutorial.com/list-controls/listbox-control/

How to create multiple buttons from existing strings in .txt file

I wonder how I can create buttons in my Toolbar by reading lines from a .txt file.
For example:
//bookmarks.txt
http://example.com
http://example2.com
http://example3.com
...
What I want is that my program on start should create a button for each line in my .txt with this event:
public void Button_Click(object sender, RoutedEventArgs e) //fire bookmark event
{
string text = e.Source.ToString().Replace("System.Windows.Controls.Button: ", "");
WebBrowser1.Navigate(text);
}
UPDATE
This is how I read the .txt:
for (int i = 0; i < File.ReadLines(#"bookmarks.txt").Count(); i++)
{
//Add button right here
}
You're trying to use WPF as if it were WinForms. This is how you would fulfil your requirements in WPF... first create a DependencyProperty collection in your Window code behind and populate it with your text entries:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(YourWindowOrUserControl));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
...
Items = new ObservableCollection<string>(File.ReadLines(#"bookmarks.txt"));
Then you simply data bind the collection to the ToolBar.ItemsSource property and declare a DataTemplate to define what each string should look like... in your case, we'll set it as the text in a Button:
<ToolBar ItemsSource="{Binding Items}">
<ToolBar.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Margin="1,0,0,0" />
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
Of course, you'll need to set the Window.DataContext to the class with your properties... the simplest way is to set it in the code behind constructor like this:
public YourWindowOrUserControl
{
InitializeComponent();
DataContext = this;
}
You must read up about how to set the DataContext properly though, as setting it this way is easy, but not necessarily correct.
Finally, you could create a class with all the necessary properties for the Button... for example, you could add a property named Text and another called Command and then make your Items property a collection of those. Then you could data bind to it like this:
<ToolBar ItemsSource="{Binding Items}">
<ToolBar.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Text}" Command="{Binding Command}" Margin="1,0,0,0" />
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
You can create buttons dynamic and add click event on fly:
Button btn = new Button();
btn.Location = new Point(yourX, yourY);
btn.Font = new Font(btn.Font.Name, 10);
btn.Text = "Text from your txt file here";
btn.ForeColor = Color.SeaShell; // choose color
btn.AutoSize = true;
btn.Click += (sender, eventArgs) =>
{
string text = btn.Text.Replace("System.Windows.Controls.Button: ", "");
WebBrowser1.Navigate(text);
};
(Insert this code in your For. Btw, you can replace the for with while. see this link)

c# How to get Listbox selection from Observable Collection

I'm probably not even asking this correctly, I am new to c#, but trying to help my 14 year-old son learn. I've created a listbox with items created with an ObservableCollection. Here is the XAML:
<ListBox x:Name="listBox1" ItemsSource="{Binding}" Margin="105,205,886,63"
IsTabStop="True" SelectionChanged="PrintText"
ScrollViewer.VerticalScrollBarVisibility="Hidden" TabIndex="5" FontSize="36"
Background="Transparent" Foreground="#FF55B64C" FontFamily="Arabic Typesetting"
FontWeight="Bold" IsDoubleTapEnabled="False" SelectionMode="Single" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="blockNameList" Text="{Binding name}"/>
<TextBlock Text=" #"/>
<TextBlock Name="blockIdList" Text="{Binding id}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is how I created the ListBox Items:
var client = new HttpClient();
var uri = new Uri("http://theurlImusing");
Stream respStream2 = await client.GetStreamAsync(uri);
// DataContractJsonSerializer ser2 = new DataContractJsonSerializer(typeof(RootObject));
// RootObject feed2 = (RootObject)ser2.ReadObject(respStream2);
DataContractJsonSerializer ser = null;
ser = new DataContractJsonSerializer(typeof(ObservableCollection<RootObject>));
ObservableCollection<RootObject> feed2 = ser.ReadObject(respStream2) as ObservableCollection<RootObject>;
var cardList = new List<RootObject>();
foreach (RootObject returnfeed in feed2)
{
string cid = returnfeed.id;
string cardname = returnfeed.name;
listBox1.Items.Add(new RootObject { id=cid, name=cardname });
}
I thought I would just use the SelectionChanged="PrintText" property of the listbox so that when I clicked on a listbox item, it would just change a textblock's text value. Ultimately, that is all I am trying to do...set a textblock or textbox to be equal to the "id" value that is clicked on in the ListBox.
void PrintText(object sender, SelectionChangedEventArgs args)
{
//What do I put in here??
}
Thanks very much for any insight! I need it!!
This is something that is much easier to do using data binding. You can bind the TextBlock.Text property directly to the ListBox using an ElementName binding:
<TextBox Text="{Binding ElementName=listBox1,Path=SelectedItem.id}" />
Alternatively, if you set set SelectedValuePath="id" on the ListBox, then binding to SelectedValue will give you the "id" property:
<ListBox x:Name="listBox1" SelectedValuePath="id" ... />
<TextBox Text="{Binding ElementName=listBox1,Path=SelectedValue}" />
As a side note (as #Rachel already noted in comments): you may as well just set the ItemsSource, rather than looping through and adding each manually. All you need is this:
listBox1.ItemsSource = feed2;
Edit
Ok, if you wanted to use the procedural approach, here's how you would do it. (No one would recommend this approach, especially if you're learning/teaching. Try to make full use of data binding, and view-viewmodel separation.)
void PrintText(object sender, SelectionChangedEventArgs args)
{
var listBox = (ListBox)sender;
RootObject selectedItem = listBox.SelectedItem;
someTextBox.Text = selectedItem.id;
}
If all you want to do is click an item in the ListBox and get it to show up in the TextBox, you don't need fancy binding (in that other answer) to do it. You can simply add a MouseUp event in the ListBox XAML:
MouseUp="ListBox1_MouseUp"
This would work similar to the SelectionChanged event you wanted to use.
You then right-click that function name in the XAML page and select "Go to definition". It will create the next function for you:
private void ListBox1_MouseUp(object sender, MouseButtonEventArgs e)
{
}
Simply add in there to update the TextBox you want with the SelectedItem values from sender:
private void ListBox1_MouseUp(object sender, MouseButtonEventArgs e)
{
ListBox lstBox = (ListBox)sender;
ListBoxItem item = lstBox.SelectedItem;
if (item != null) // avoids exception when an empty line is clicked
{
someBox.Text = item.name;
someOtherBox.Text = item.id;
}
}
I later found that blockNameList and blockIdList are not accessible via intellisense because they are within the DataTemplate of the ListBox, so I put someBox and someOtherBox, as references to other TextBoxes you would have to add to the XAML, outside of the ListBox. You would not re-write data inside the ListBox on the same item by clicking it. Even if you could reach the template's TextBlock to do it, you'd just be re-writing that same item with its own values, since it would be the SelectedItem!
Even though there are those that don't recommend this approach because they like binding everything - and in some cases you want binding to occur so that controls on the page update as a result of dependencies (i.e. do one thing to cause another), I find that manual methods of clicking a button/item/control to update something are just fine and avoid all the model/MVVM BS that has taken over WPF and over-complicated it.

Categories