WPF Add trigger to an existing style from code behind - c#

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.

Related

Element already has a logical parent. It must be detached from the old parent before it is attached to a new one"

In listbox binding the values from ItemsSource. If doubleclick any item from listbox, it will clear the itemsSource value it will add the combobox control to the specified listboxselected index. How to achieve this?
Now it shows like "Element already has a logical parent. It must be detached from the old parent before it is attached to a new one"
//Get the index value of selected Item
var index = lstbxindex.Items.IndexOf(lstbxindex.SelectedItem);
m_combobox.Visibility = Visibility.Visible;
projectInformationList = null;
lstbxindex.ItemsSource = null;
lstbxindex.Items.Clear();
lstbxindex.Items.Insert(index, m_combobox); //InvalidOperationException thrown
m_combobox.Focus();
To Remove the Items from list I used this code,
IEditableCollectionView items = lstbxindex.Items;
if (items.CanRemove)
{
items.Remove(lstbxindex.SelectedItem);
}
I don't know how to add the control in listbox selectedindex while itemsSource is in use.
I tried ->
listbox.Items.Add(combox); it shows, can't able to add items while itemsSource is in use but in this code I didn't mention the selected index. But I want to add a control for listbox selectedindex position while double click any items from listbox.
Edit
I tried to add the items without using itemsSource like,
foreach (DTorow rowdata in table.Rows)
{
lstbox.Items.Add(rowdata .Name);
}
But it did not show the values in listbox. If it is shows the value in listbox simply add a combobox into specified index using this code listbox.Items.Insert(0"combobox) it will not shows invalidException(itemsSource is in use can't able to add new item).
Here is a simple example of how to do it with the standard WPF approach, i.e. MVVM and data templating. As already said in a comment to your previous question, you'll have to read the Data Binding Overview and Data Templating Overview articles to understand it, but this is unavoidable if you want to learn WPF.
The view model and the MainWindow code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.Items.Add(new DataItem { Text = "Item 1" });
vm.Items.Add(new DataItem { Text = "Item 2" });
vm.Items.Add(new DataItem { Text = "Item 3" });
vm.Items.Add(new DataItem { Text = "Item 4" });
DataContext = vm;
}
}
public class DataItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string text;
public string Text
{
get { return text; }
set
{
text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
public class ViewModel
{
public ObservableCollection<DataItem> Items { get; }
= new ObservableCollection<DataItem>();
}
And the XAML with two different ControlTemplates of a ListBoxItem, depending on
whether it's selected or not:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<TextBlock Margin="5,3" Text="{Binding Text}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<TextBox Text="{Binding Text}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

How to Programatically Add Images to WPF Tab Control Item

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);

Make select controls visible based on selected tab using xaml

I've got following code:
private Dictionary<int, UserControl> tabControls = new Dictionary<int, UserControl>();
public MainWindow()
{
InitializeComponent();
tabControls[0] = new Panel1();
tabControls[1] = new Panel2();
tabControls[2] = new Panel3();
tabControls[3] = new Panel4();
tabControls[4] = new Panel5();
tabControls[5] = new Panel6();
tabControls[6] = new Panel7();
tabControls[7] = new Panel8();
}
public object SelectedTab
{
//this is assigned from xaml binding
set
{
OnCurrentTabChanged(tabControl.SelectedIndex);
}
}
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
}
Every time the user selects different tab, an other control appears.
Is there any way to simplify this using xaml?
I cannot put the controls themselves inside the tab control
I've done this before with another TabControl which has it's headers and frame hidden. Then I just bind the SelectedIndex to your other tab's SelectedIndex, and the two are synchronized
<!-- TabControl without the TabHeaders -->
<Style x:Key="TabControl_NoHeadersStyle" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<DockPanel>
<!-- This is needed to draw TabControls with Bound items -->
<StackPanel IsItemsHost="True" Height="0" Width="0" />
<ContentPresenter x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then you can setup your two tab controls, each bound to different sources, and bind the SelectedIndex of one to the SelectedIndex of the other
<TabControl x:Name="MainTabControl" />
<TabControl ItemsSource="{Binding Panels}"
SelectedIndex="{Binding ElementName=MainTabControl, Path=SelectedIndex}"
Style="{StaticResource TabControl_NoHeadersStyle}" />
Another alternative is to bind the SelectedIndex to something in your code-behind, then anytime it changes, raise a PropertyChanged notification on another property that exposes the panel you want to display.
<TabControl SelectedIndex="{Binding SelectedTabIndex} />
<ContentControl Content="{Binding SelectedPanel}" />
and in the code behind
public int SelectedTabIndex
{
get { return _selectedTabIndex;}
set
{
if (_selectedTabIndex != value)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
RaisePropertyChanged("SelectedPanel");
}
}
}
public UserControl SelectedPanel
{
get { return tabControls[SelectedTabIndex]; }
}
TabItem has an IsSelected propery you could bind to that I think would simplify the syntax.
public bool TabIsSelected
{
get { return tabIsSelected; }
set
{
if (value && dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
tabIsSelected = value;
}
But I still don't get why you can't just put the control in the tabitem?
using codebehind
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
UIElemnt[] pp = dataDisplay.Children.Cast<UIElement>().ToArray();
Array.ForEach(pp, x=> x.visibility = Visibility.Collapsed);
pp[tabIndex].visibility = Visibility.Visible;
}
}

How add ControlTemplate to Content?

I have ControlTemplate (XAML).
I need in code behid add ControlTemplate to TabItem.Content.
var tabItem = new TabItem
{
DataContext = listDesk,
Header = headerText,
Content = ???
};
XAML
<ControlTemplate x:Key="MyTabItemContentTemplate" TargetType="controls:TabItem">
<StackPanel>
<TextBlock Text="wwwwww"/>
</StackPanel>
</ControlTemplate>
I use SL4
I think you're confusing concepts. Instead of setting the Content, which is data, set the Template, which is the visual representation of the control:
var tabItem = new TabItem
{
DataContext = listDesk,
Header = headerText,
Template = this.FindResource("MyTabitemContentTemplate") as ControlTemplate
};
Moreover, chances are there is no reason to do this in code. You could be doing it entirely in XAML.

Programmatically Creating Image Button in WPF

I want to create a Windows Toolbar (something like RocketDock) which dynamically creates buttons from serialized classes, but I can't figure out how to dynamically set the image.
As an aside, how should a image be serialized (i want to serialize the image with the storage class, instead of loading it from the original png file location each time)?
I found the following code to create a dependency property
public class ImageButton
{
#region Image dependency property
public static readonly DependencyProperty ImageProperty;
public static ImageSource GetImage(DependencyObject obj)
{
return (ImageSource)obj.GetValue(ImageProperty);
}
public static void SetImage(DependencyObject obj, ImageSource value)
{
obj.SetValue(ImageProperty, value);
}
#endregion
static ImageButton()
{
var metadata = new FrameworkPropertyMetadata((ImageSource)null);
ImageProperty = DependencyProperty.RegisterAttached("Image",
typeof(ImageSource),
typeof(ImageButton), metadata);
}
}
And i created a button style to inherit to set the image
<Style x:Key="ImageButton" TargetType="{x:Type Button}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Image Source="{Binding Path=(Extender:ImageButton.Image), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"
HorizontalAlignment="Left" Margin="8,0,0,0" Height="16" Width="16" />
<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
but I can't figure out how to set the image from code after I create the button
var newButton = new Button();
var style = (Style)FindResource("ImageButton");
newButton.Image does not resolve. Do I need to do something with style.Resources?
EDIT - response to Olli
Thanks, but... Using...
Link link = new Link(#"c:\temp\dev\ClearAllIcon.png");
var newButton = new Button();
newButton.Content = "bloberific";
var style = (Style)FindResource("ImageButton");
ImageButton.SetImage(newButton, link.IconSource);
MainStack.Children.Add(newButton);
where link is defined as
public Link(string iconPath)
{
IconPath = iconPath;
IconSource = new BitmapImage(new Uri(iconPath));
}
the image does not show, though the text does. The button is normal looking.
You did not create a normal dependency property but an attached dependency property. Your newButton instance has no defined property "Image". Therfore you do not have the convenience CLR property getter/setter (see MSDN for details).
The code should look similar to this to set the image source value:
ImageButton.SetImage(newButton, imageSource);
Link link = new Link(#"c:\temp\dev\ClearAllIcon.png");
var newButton = new Button();
newButton.Content = "bloberific";
var style = (Style)FindResource("ImageButton");
ImageButton.SetImage(newButton, link.IconSource);
MainStack.Children.Add(newButton);
you do not use the style variable - is this the problem ?
This worked for me some time ago to create a little game.
var botao = sender as Button;
Image i = new Image();
i.Source= new BitmapImage(new Uri(#"/Resources/x.png", UriKind.RelativeOrAbsolute));
botao.Content = i;

Categories