I am getting my buttons to appear, but their styling is not displaying and i'm not sure why.
Below is the XAML code and how it is defined in the view model.
private ObservableCollection<Button> myButtons;
public ObservableCollection<Button> MyButtons
{
get { return myButtons; }
set
{
if (myButtons == null)
{
myButtons = value; OnPropertyChanged("MyButtons");
}
}
}
private void PopulateButtons()
{
List<Button> buttonsToAdd = new List<Button>();
List<string> buttonsToAdd = new List<string>();
foreach (var item in SettingsSingleton.RowColumnOptions)
{
int total = item.Key;
Tuple<int, int> rowColumn = item.Value;
buttonsToAdd.Add((total).ToString());
}
MyButtons = new ObservableCollection<Button>(buttonsToAdd);
}
And XAML is
<StackPanel DockPanel.Dock="Top"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl ItemsSource="{Binding MyButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Create_Click}" CommandParameter="{Binding Content}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource NiceStyleButton}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="20"/>
<Setter Property="Padding" Value="6"/>
<Setter Property="FontSize" Value="42"/>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
I also get a weird warning/error:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='Button'
Can anyone help? Thank you.
EDIT:
My Create_Click command and the functions that then get called by it.
private ICommand createClickCommand;
public ICommand Create_Click
{
get
{
if (createClickCommand == null)
{
createClickCommand = new RelayCommand(CreateGrid);
}
return createClickCommand;
}
}
private void CreateGrid(object param)
{
Generate(param);
}
And RowColumnOptions is basically just a tuple of int, int, where we define a row and column. This is used for the Grid creation. This is where RowColumnOptions gets populated.
public static class SystemSettingsSingleton
{
public static Dictionary<int, Tuple<int, int>> RowColumnOptions = new Dictionary<int, Tuple<int, int>>();
public static void SetOptions(List<string> Options)
{
// Let's parse our options
foreach (var option in Options)
{
var rowAndColumnSettings = option.Split('x');
// Check that we have both row and column count
int rowCount, columnCount = 0;
if (rowAndColumnSettings.Length == 2 && int.TryParse(rowAndColumnSettings[0], out rowCount)
&& int.TryParse(rowAndColumnSettings[1], out columnCount))
{
RowColumnOptions.Add( (rowCount * columnCount),
new Tuple<int, int>(rowCount, columnCount) );
}
}
}
}
That SetOptions method is coming from a static class.
You shouldn't have an observable collection of Button. That's never a good idea. It's telling you it's ignoring the ItemTemplate because you are giving it controls in the collection, which is unnecessary. Then you're trying to create another button in the item template. Templates in WPF don't mean what you think they do: They don't style content, they create content. Don't create buttons in your viewmodel, create them in your item template. Your ObservableCollection should just provide the information the item template will need to create the buttons you want to see. Here, that's just one string.
But it turns out that your options really consist of tuples. You want to display their products in the button content, and you want to pass the tuples themselves as the command parameter. This is easy.
public class ViewModel : ViewModelBase
{
public Dictionary<int, Tuple<int, int>> RowColumnOptions
=> SystemSettingsSingleton.RowColumnOptions;
private void PopulateButtons()
{
OnPropertyChanged(nameof(RowColumnOptions));
}
// I don't know what your relay command class looks like, I just tossed one
// together that has an Action<object>
public ICommand Create_Click { get; } = new RelayCommand(param =>
{
var tuple = param as Tuple<int, int>;
MessageBox.Show($"Parameter: {tuple.Item1} x {tuple.Item2}");
});
}
And the ItemsControl:
<ItemsControl ItemsSource="{Binding RowColumnOptions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--
The DataContext here is a KeyValuePair<int, Tuple<int, int>>
The product of x * y was the Key, so that's what we'll display in the button's
Content.
We could display all three values if we wanted to.
We want to pass the tuple to the command, and that's the Value of the KeyValuePair.
So we bind that to CommandParameter
-->
<Button
Command="{Binding DataContext.Create_Click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding Value}"
Content="{Binding Key}"
>
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource NiceStyleButton}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="20"/>
<Setter Property="Padding" Value="6"/>
<Setter Property="FontSize" Value="42"/>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Some questions linger in my mind: What if you have both "2x6" and "3x4" in that initial list of options? The first one that comes in will be replaced by the second. Is that the desired behavior?
Related
I don't know if need to combine DataTrigger & Trigger, if there's better way please tell me.
My goal is, to create a menu(with icons), icons will change while meet hover or selected event.
Here's an enum define all menu types:
public enum PageTypes:byte
{
NotSet = 0,
HomePage = 1,
ShopPage = 2,
AboutPage = 3
}
Then I created a MenuItemModel represent each menu item:
public class MenuItemModel : INotifyPropertyChanged
{
private PageTypes _menuItemType = PageTypes.NotSet;
public PageTypes MenuItemType { get { return _menuItemType; } set { if (value != _menuItemType) { _menuItemType = value; RaisePropertyChanged(() => MenuItemType); } } }
private bool _isSelected = false;
public bool IsSelected { get { return _isSelected; } set { if (value != _isSelected) { _isSelected = value; RaisePropertyChanged(() => IsSelected); } } }
}
Ok, then I begin to create UI.
<!-- MenuItem Template -->
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/Image/Home_normal.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding MenuItemType}" Value="AboutPage">
<Setter Property="Source" Value="/Image/About_normal.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
</DataTemplate>
till now everything is very easy, but when I try to make mouseOver and Selected effect, problem comes.
for example, if mouse over home_normal.png, it should change to home_hover.png, if IsSelected property is TRUE, image should be ignore hover trigger then use home_selected.png. But there's 3 image, how do I know what image should change?
<!-- MenuItem Template -->
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/Image/Home_normal.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding MenuItemType}" Value="AboutPage">
<Setter Property="Source" Value="/Image/About_normal.png"/>
</DataTrigger>
<!-- MY PLAN -->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="?_hover.png"/>
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Source" Value="?_selected.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
</DataTemplate>
If you can see the question mark in "MY PLAN" comment, that would be my question: what should I do in the Value field?
You can use MultiDataTrigger like this. But you should add same 3 triggers for all types of pages. Note that next trigger overrides below and conditions works like logical AND.
<p:Style.Triggers xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<DataTrigger Binding="{Binding MenuItemType}" Value="ShopPage">
<Setter Property="Source" Value="/Image/Shop_normal.png"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding MenuItemType}" Value="ShopPage" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsMouseOver}" Value="true" />
</MultiDataTrigger.Conditions>
<Setter Property="Source" Value="/Image/Shop_MouseOver.png" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding MenuItemType}" Value="ShopPage" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected}" Value="true" />
</MultiDataTrigger.Conditions>
<Setter Property="Source" Value="/Image/Shop_IsSelected.png" />
</MultiDataTrigger>
</p:Style.Triggers>
In my opinion, the answer you've already received and accepted is a good one. It's entirely XAML-based, which seems to be a primary goal in your scenario, and it should work very well. That said, the XAML-only solution is fairly verbose and involves a lot of redundant code. This is already seen in the scenario above where you have two buttons types, each with three possible states. And it will only get worse as you add button types and states.
If you are willing to do a little code-behind, I think you can accomplish the same effect but with a lot less redundancy.
Specifically, if you use <MultiBinding>, you can bind the relevant properties to a collection that can be used to look up the correct image source. In order for me to accomplish this, I needed to create a couple of small container types to store the lookup data, and of course the IMultiValueConverter implementation to use them:
Container types:
[ContentProperty("Elements")]
class BitmapImageArray
{
private readonly List<ButtonImageStates> _elements = new List<ButtonImageStates>();
public List<ButtonImageStates> Elements
{
get { return _elements; }
}
}
class ButtonImageStates
{
public string Key { get; set; }
public BitmapImage[] StateImages { get; set; }
}
Converter:
class OrderedFlagConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BitmapImageArray imageData = (BitmapImageArray)parameter;
string type = (string)values[0];
foreach (ButtonImageStates buttonStates in imageData.Elements)
{
if (buttonStates.Key == type)
{
int index = 1;
while (index < values.Length)
{
if ((bool)values[index])
{
break;
}
index++;
}
return buttonStates.StateImages[index - 1];
}
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value,
Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In your example, using the above might look something like this:
<DataTemplate x:Key="MenuTemplate">
<Button Command="{Binding ClickCommand}" CommandParameter="{Binding}">
<Image>
<Image.Source>
<MultiBinding>
<MultiBinding.Converter>
<l:OrderedFlagConverter/>
</MultiBinding.Converter>
<MultiBinding.ConverterParameter>
<l:BitmapImageArray>
<l:ButtonImageStates Key="ShopPage">
<l:ButtonImageStates.StateImages>
<x:Array Type="{x:Type BitmapImage}">
<BitmapImage UriSource="/Image/Shop_selected.png"/>
<BitmapImage UriSource="/Image/Shop_hover.png"/>
<BitmapImage UriSource="/Image/Shop_normal.png"/>
</x:Array>
</l:ButtonImageStates.StateImages>
</l:ButtonImageStates>
<l:ButtonImageStates Key="AboutPage">
<l:ButtonImageStates.StateImages>
<x:Array Type="{x:Type BitmapImage}">
<BitmapImage UriSource="/Image/About_selected.png"/>
<BitmapImage UriSource="/Image/About_hover.png"/>
<BitmapImage UriSource="/Image/About_normal.png"/>
</x:Array>
</l:ButtonImageStates.StateImages>
</l:ButtonImageStates>
</l:BitmapImageArray>
</MultiBinding.ConverterParameter>
<Binding Path="ButtonType"/>
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
<Binding Path="IsSelected"/>
</MultiBinding>
</Image.Source>
</Image>
</Button>
</DataTemplate>
The converter takes, as input, bindings to the properties that affect the visual state of the button. The first bound value is simply the type of the button; this is used to look up the correct array of button states for the button. The remaining bound values (you can have arbitrarily many in this approach) are flags that are searched; the images are stored in the same order as the flags, with one additional "default" image at the end (i.e. if no flags are set, the default image is returned).
In this way, adding new button types involves only adding a new ButtonImageStates object, specifying the correct key for that button type, and adding new button states involves only adding a single line to each button type's list: the BitmapImage reference that corresponds to the image for that state for that button type.
Doing it this way drastically cuts down on the amount of code one has to add as new button types and states are needed: a given button type need be mentioned in the XAML only once, and likewise each triggering property is mentioned only once. A XAML-only approach will require a lot of duplicated boilerplate, and the actual image file references will be scattered throughout the style declaration.
Here is a simple demo of the basic technique. Lacking a good MCVE to start with, I didn't want to waste time re-creating parts of the code that weren't strictly necessary for the purposes of a demonstration:
I only bothered to create four state images, and of course only wrote code to deal with four possible states: two each for two different button types.
I also didn't bother with putting this in a menu; I'm just using a plain ItemsControl to present the buttons.
Naturally, the view model is a degenerate class; I didn't bother with property-change notification, since it's not needed here. The example still works if you include that though.
Here are the images used in the example (I'm a programmer, not an artist…I considered not even bothering with image content, since that's also not strictly required to demonstrate the basic technique, but figured I could handle four basic images :) ):
These are added to the project in a "Resources" folder, with the Build Action set to Resource.
XAML:
<Window x:Class="TestSO34193266MultiTriggerBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO34193266MultiTriggerBinding"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<l:OrderedFlagConverter x:Key="orderedFlagConverter1"/>
<BitmapImage x:Key="bitmapRedNormal"
UriSource="pack://application:,,,/Resources/red_normal.png"/>
<BitmapImage x:Key="bitmapRedHover"
UriSource="pack://application:,,,/Resources/red_hover.png"/>
<BitmapImage x:Key="bitmapGreenNormal"
UriSource="pack://application:,,,/Resources/green_normal.png"/>
<BitmapImage x:Key="bitmapGreenHover"
UriSource="pack://application:,,,/Resources/green_hover.png"/>
<l:ViewModel x:Key="redViewModel" ButtonType="Red"/>
<l:ViewModel x:Key="greenViewModel" ButtonType="Green"/>
<x:Array x:Key="items" Type="{x:Type l:ViewModel}">
<StaticResource ResourceKey="redViewModel"/>
<StaticResource ResourceKey="greenViewModel"/>
</x:Array>
<x:Array x:Key="redButtonStates" Type="{x:Type BitmapImage}">
<StaticResource ResourceKey="bitmapRedHover"/>
<StaticResource ResourceKey="bitmapRedNormal"/>
</x:Array>
<x:Array x:Key="greenButtonStates" Type="{x:Type BitmapImage}">
<StaticResource ResourceKey="bitmapGreenHover"/>
<StaticResource ResourceKey="bitmapGreenNormal"/>
</x:Array>
<l:BitmapImageArray x:Key="allButtonStates">
<l:ButtonImageStates Key="Red" StateImages="{StaticResource redButtonStates}"/>
<l:ButtonImageStates Key="Green" StateImages="{StaticResource greenButtonStates}"/>
</l:BitmapImageArray>
<ItemsPanelTemplate x:Key="panelTemplate">
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
<DataTemplate x:Key="template" DataType="l:ViewModel">
<Button>
<Image Stretch="None">
<Image.Source>
<MultiBinding Converter="{StaticResource orderedFlagConverter1}"
ConverterParameter="{StaticResource allButtonStates}">
<Binding Path="ButtonType"/>
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Image.Source>
</Image>
</Button>
</DataTemplate>
<!-- explicit namespace only for the benefit of Stack Overflow formatting -->
<p:Style TargetType="ItemsControl"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Setter Property="ItemsSource" Value="{StaticResource items}"/>
<Setter Property="ItemsPanel" Value="{StaticResource panelTemplate}"/>
</p:Style>
</Window.Resources>
<StackPanel>
<ItemsControl ItemTemplate="{StaticResource template}"/>
</StackPanel>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
class ViewModel
{
public string ButtonType { get; set; }
}
class OrderedFlagConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
BitmapImageArray imageData = (BitmapImageArray)parameter;
string type = (string)values[0];
foreach (ButtonImageStates buttonStates in imageData.Elements)
{
if (buttonStates.Key == type)
{
int index = 1;
while (index < values.Length)
{
if ((bool)values[index])
{
break;
}
index++;
}
return buttonStates.StateImages[index - 1];
}
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
[ContentProperty("Elements")]
class BitmapImageArray
{
private readonly List<ButtonImageStates> _elements = new List<ButtonImageStates>();
public List<ButtonImageStates> Elements
{
get { return _elements; }
}
}
class ButtonImageStates
{
public string Key { get; set; }
public BitmapImage[] StateImages { get; set; }
}
One minor note: for some reason I get in the XAML editor the following error message on the <Window> element declaration:
Collection property 'TestSO34193266MultiTriggerBinding.ButtonImageStates'.'StateImages' is null.
I've clearly failed to jump through some hoop the XAML editor wants me to clear with respect to the declaration and/or implementation of ButtonImageStates, but what that is I don't know. The code compiles and runs just fine, so I haven't bothered to try to figure that part out. It may well be the case that there's a better way to represent the map of button state images, but this way works and other than the spurious error seems fine to me.
I had a checkbox all column inside the datagrid in WPF C#.
<DataGridCheckBoxColumn Binding="{Binding IsSelected,UpdateSourceTrigger=PropertyChanged}" CanUserSort="False">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate x:Name="dtAllChkBx">
<CheckBox Name="cbxAll" HorizontalAlignment="Center" Margin="0,0,5,0" IsEnabled="{Binding Path=DataContext.IsCbxAllEnabled,RelativeSource={RelativeSource AncestorType=DataGrid}}"
IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid},UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
When I check the All checkbox, of course, it will mark all the checkboxes, but once I uncheck one checkbox, the All checkbox is still checked. This should be unchecked. How should I do that using WPF C#.
If I understood you correctly - after any change of IsSelected property inside collection item you should update AllSelected value.
So, you need some callback inside all your items(event or Action or any mechanism you want) and change get logic for AllSelected
Here is some draft for item IsSelected property and constructor:
public bool IsSelected {
get { return isSelected; }
set {
isSelected = value;
OnPropertyChanged();
if (globalUpdate != null) globalUpdate();
}
}
public ItemClass(Action globalUpdate, ...your parameters) {
this.globalUpdate = globalUpdate;
...do smth with your parameters
}
Example of usage:
new ItemClass(() => OnPropertyChanged("AllSelected"))
And of course don't forget about AllSelected getter
public bool AllSelected {
get { return YourGridItemsCollection.All(item => item.IsSelected); }
Now when you check manually all items then AllSelected will be automatically checked, and unchecked when you uncheck any item.
I am wondering if anyone could explain me the difference between
binding a selected value of a Collection to a comboBox.
Or Binding the value to a Button Content.
Like that
<ComboBox x:Name="_culturedTitleViewModelSelector" Visibility="Hidden" Style="{StaticResource ResourceKey=_culturedTitleViewModelSelectorStyle}"
ItemsSource="{Binding Path=AvailableCultures, Source={x:Static Localized:ResourcesManager.Current}}"
SelectedValue="{Binding Path=CurrentCulture, Source={x:Static Localized:ResourcesManager.Current}}"
<Button x:Name="LanguageBtn" Content="{Binding Path=CurrentCulture, Source={x:StaticLocalized:ResourcesManager.Current}}"
The issue is If i Don't use the ComboBox up there, the DependencyProperty I Have in another class is not being called.
But if I Use the comboBox everything works...
Altought the comboBox doesnt do anything it's just a "workarround"
In my CS code when i CLick on my button I DO that :
ResourcesManager.Current.SwitchToNextCulture();
//We use a dummy comboBox to make sure the LanguageBehavior Property is being notified.
_culturedTitleViewModelSelector.SelectedItem = ResourcesManager.Current.CurrentCulture;
And if I Dont set the SelectedItem of the combobox to another culture. My languageBehavior class is not notified.
:
public class LanguageBehavior
{
public static DependencyProperty LanguageProperty =
DependencyProperty.RegisterAttached("Language",
typeof(string),
typeof(LanguageBehavior),
new UIPropertyMetadata(OnLanguageChanged));
public static void SetLanguage(FrameworkElement target, string value)
{
target.SetValue(LanguageProperty, value);
}
public static string GetLanguage(FrameworkElement target)
{
return (string)target.GetValue(LanguageProperty);
}
private static void OnLanguageChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as FrameworkElement;
if (e.NewValue!=null)
element.Language = XmlLanguage.GetLanguage(e.NewValue.ToString());
}
}
I'd expect ComboBox Content to work the same as Button Content.
In my Generic.Xaml i do that :
<Style TargetType="{x:Type TextBlock}" x:Key="_textBlockLanguageProperty">
<Setter Property="WpfServices:LanguageBehavior.Language" Value="{Binding Path=CurrentCulture, Source={x:Static Localized:ResourcesManager.Current}}"
/>
</Style>
And that is CurrentCulture
public CultureInfo CurrentCulture
{
get { return CultureProvider.Current; }
set
{
if (value != CultureProvider.Current)
{
CultureProvider.Current = value;
OnCultureChanged();
}
}
}
Current :
public static ResourcesManager Current
{
get
{
if (_resourcesManager == null)
{
var cultureProvider = new BaseCultureProvider();
_resourcesManager = new ResourcesManager(cultureProvider);
_resourcesManager.Init();
}
return _resourcesManager;
}
}
EDIT :
My _culturedTitelViewModelSelectorStyle is
<Style TargetType="{x:Type ComboBox}" x:Key="_culturedTitleViewModelSelectorStyle">
<Setter Property="DisplayMemberPath" Value="DisplayName" />
<Setter Property="SelectedValuePath" Value="." />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="MaxHeight" Value="40" />
<Setter Property="FontSize" Value="20" />
<Setter Property="Margin" Value="5" />
<Setter Property="SelectedIndex" Value="0" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
In the ComboBox you are binding the SelectedValue to a specific culture. This will select that culture from the list of available cultures, and therefor, trigger a set on the CurrentCulture property.
The Content property of a Button is merely displaying something to the user, it is not doing any assigning. It reads the property value and then displays it. That is why you need to manually change the Culture in the Click event to get it to do anything.
If you want the user to be able to select a value from a list of available values, a ComboBox or ListBox is the way to go. A Button is for triggering a specific action, not for selecting from a list.
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;
}
}
I have a simple class:
public class Foo
{
public string Text { get; set; }
public bool AppleStyle { get; set; }
public Foo(string text, bool applyStyle)
{
Text = text;
ApplyStyle = applyStyle;
}
public override string ToString()
{
return Text;
}
}
Which is then used to add items to a ListBox:
var one = new Foo("Some Text", false);
var two = new Foo("More Text", true);
MyListBox.Items.Add(one);
MyListBox.Items.Add(two);
I then loop through the items in the ListBox to figure out how to style them. This is where I get stuck. I tried inheriting from ListBoxItem for the class, but no items get added if I do that.
for (int i = 0; i < MyListBox.Items.Count; i++)
{
if(((Foo)MyListBox.Items[i]).ApplyStyle)
{
((ListBoxItem)MyListBox.Items[i]).Style = Resources["MyStyle"] as Style;
}
}
Update:
In MainWindow.xaml:
<Window.Resources>
<Style x:Key="MyStyle" TargetType="ListBoxItem">
<Setter Property="Background" Value="Bisque"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
</Style>
</Window.Resources>
Update 3:
Making some progress, just need to know how to refresh the styles (after clicking on a button). Plus if Resource is not in MainWindow.xaml, would it then look in App.xaml before returning null?
MainWindow.xaml
<Window...>
<Window.Resources>
<Style x:Key="MyClass" TargetType="ListBoxItem">
<Setter Property="Background" Value="Bisque"></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
</Style>
<myapp:MyListItemStyleSelector x:Key="MyListItemStyleSelector" />
</Window.Resources>
<Grid>
...
<ListBox .... ItemContainerStyleSelector="{StaticResource: MyListItemStyleSelector}" />
...
</Grid>
</Window>
MyListItemStyleSelector.cs
public class MyListItemStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
int index = ic.ItemContainerGenerator.IndexFromContainer(container);
Style applyStyle = null;
var data = item as Foo;
if (data != null && data.ApplyStyle)
{
applyStyle = ic.TryFindResource("MyStyle") as Style;
}
return applyStyle;
}
}
I think you have some sort of mixup here, i try to explain as good as i can.
First of all You usually never need to change the Style in code, like your last code block.
One thing that is difficult to understand in the beginning is the use of a ItemContainerStyle and DataTemplate.
I would suggest that you do the following.
Instead of changing the style off your ListBoxItem see if it is sufficient to use a DataTemplate. The DataTemplate defines how the Content of your ListBoxItem is shown.
<DataTemplate TargetType="{x:Type Foo}">
<!-- your visuals and controls here -->
</DataTemplate>
Now if you want to use different datatemplates you could use different classes and create different DataTemplates for them, or you use a DataTemplateSelector
public class FooTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
var mdl = item as Foo;
if( mdl.AppleStyle )
return element.FindResource("appleTemplate") as DataTemplate;
return element.FindResource("normalTemplate") as DataTemplate;
}
}
Create that templateselector in xaml and reference it in your listbox
<myNs:FooTemplateSelector x:Key="fooTemplateSelector"/>
<Listbox DataTemplateSelector="{StaticResource fooTemplateSelector}"/>
now you need to create 2 DataTemplates appleTemplate *normalTemplate* and you can easyl distinguish which data template to use vial the selector. Which is done automatically in the ListBox for you.
If you really want to change the Style of the ItemContainer you can use ItemContainerStyleSelector which works similar to the DataTemplateSelector. But i would not suggest it. You should supply the content and leave the ListBoxItem as it is, only if you want to modify the design(in this case, the selection color etc.), otherwise it might confuse the user or break functionality.
If you add data-objects directly to the ListBox the container-items will be generated automatically, you cannot get them this way.
Use the ItemContainerGenerator:
((ListBoxItem)MyListBox.ItemContainerGenerator.ContainerFromIndex(i)).Style = Resources["MyStyle"] as Style;
Why not do this in the XAML?
<ListBox Name="MyListBox">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding ApplyStyle}" Value="True">
<Setter Property="Background" Value="Bisque" />
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
But your overall problem is that ListBox.Items returns a collection of data objects, not XAML Controls. To get the XAML control that contains the Data Object you have to do as H.B. suggested and use MyListBox.ItemContainerGenerator.ContainerFromItem(dataObject) to get the XAML Container for the data object. Just be sure you wait until after the ItemContainerGenerator has finished rendering items to get the container (I believe it has a Status property or StatusChanged event you can use... it's been a while and I can't remember the exact syntax)