I am trying to learn how to use the IValueConverter to enable/disable controls (in this case a button) using the boolean-property of an item selected in a listbox. The button in question is the "TestButton". I believe I have the XAML for it setup correctly but it does not enable when I add and select a user defined item or disable when I select one of the other items in the listbox. The listbox contains a list of "Drink" objects that can be added to. When one is added it becomes a user defined item. Now I believe I am missing something but I am not sure what. Any help will be appreciated.
ViewModel code here:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:Drinks x:Key="Drink"/>
<local:EnableConverter x:Key="EnableConverter"/>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox x:Name="DrinksListBox" HorizontalAlignment="Center" Height="325" Width="275" Margin="0,0,0,0" VerticalAlignment="Center" Grid.Column="0" ItemsSource="{Binding SomeDrinks}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Type}" Width="80" Margin="0,0,10,0" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Width="80" Margin="0,0,10,0" Grid.Column="1" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding NumberOfCans}" Width="80" Margin="0,0,10,0" Grid.Column="2" HorizontalAlignment="Left"/>
<Button x:Name="DrinkDeleteButton" Content="Delete" Visibility="{Binding Path=IsUserDefined, Converter={StaticResource BoolToVis}}" Click="CmdDeleteDrink_Clicked" HorizontalAlignment="Left" Grid.Column="3"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="DrinkNameTextBox" Grid.Column="1" HorizontalAlignment="Left" Height="45" Margin="0,0,0,100" TextWrapping="Wrap" Text="Enter Drink Name" VerticalAlignment="Center" Width="240" FontSize="20" VerticalContentAlignment="Center"/>
<TextBox x:Name="NumberCansTextBox" Style="{StaticResource textStyleTextBox}" Grid.Column="1" HorizontalAlignment="Left" Height="45" Margin="0,0,0,150" VerticalAlignment="Bottom" Width="240" FontSize="20" VerticalContentAlignment="Center">
<TextBox.Text>
<Binding Path="NumberOfCans" UpdateSourceTrigger="PropertyChanged" Source="{StaticResource Drink}">
<Binding.ValidationRules>
<local:NumberCansValidationRule Min="0" Max="10"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<ComboBox x:Name="DrinkTypeComboBox" Grid.Column="1" HorizontalAlignment="Left" Margin="0,47,0,0" VerticalAlignment="Top" Width="240" Height="45" ItemsSource="{Binding Drinks, Mode=OneWay}" DisplayMemberPath="Type" FontSize="20"/>
<Button x:Name="AddDrinkButton" Content="Add Drink" Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,10,100" VerticalAlignment="Center" Width="100" Height="45" Click="CmdAddDrink_Clicked">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=NumberCansTextBox}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button x:Name="TestButton" Content="Test" IsEnabled="{Binding Path=IsUserDefined, UpdateSourceTrigger=PropertyChanged, Source={StaticResource Drink}, Converter={StaticResource EnableConverter}}" Grid.Column="1" HorizontalAlignment="Right" Margin="0,70,10,0" VerticalAlignment="Center" Width="100" Height="45"/>
</Grid>
Here is my converter:
namespace WpfApp1
{
class EnableConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isenable = false;
bool b = (bool)value;
if (b == true)
{
isenable = true;
}
return isenable;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
This class contains the "IsUserDefined" property that I am binding to:
public class Drinks
{
private string type;
private string name;
private int numCans;
public Drinks() { }
public Drinks(string type, string name, int numCans)
{
this.type = type;
this.name = name;
this.numCans = numCans;
}
public bool IsUserDefined { get; set; }
public string Type
{
get { return type; }
set
{
if (type != value)
{
type = value;
}
}
}
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
}
}
}
public int NumberOfCans
{
get { return numCans; }
set
{
if (numCans != value)
{
numCans = value;
}
}
}
}
Related
I have a UWP application, and it has a page containing a few textbox controls bound to A.B.C[2].D, A.B.C[2].E, A.B.C[2].F, and so on
Now I want to move the text boxes to a separate UserControl to simplify my page's XAML but
still want them to be bound to A.B.C[2].D, A.B.C[2].E etc.
How can I achieve this?
Thank you
Here is my UserControl
<UserControl
x:Class="Inspecto.HWStatusDisplayControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Truphase360"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="250">
<Grid>
<Grid.Resources>
<Style x:Key="styleTxtBox" TargetType="TextBox">
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Width" Value="75"/>
</Style>
<Style x:Key="styleTxtBlk" TargetType="TextBlock">
<Setter Property="FontSize" Value="12" />
<Setter Property="Margin" Value="10"/>
<Setter Property="Width" Value="75" />
</Style>
</Grid.Resources>
<StackPanel Orientation="Vertical" Margin="20">
<TextBlock Text="{x:Bind Header}" FontSize="16" FontWeight="Bold" Margin="10">
</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Temp1" Style="{StaticResource styleTxtBlk}" />
<TextBox Style="{StaticResource styleTxtBox}" Text="{Binding Mode=OneWay, Path=Temperature1 }" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Temp2" Style="{StaticResource styleTxtBlk}" />
<TextBox Style="{StaticResource styleTxtBox}" Text="{Binding Mode=OneWay, Path=Temperature2 }" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="SW Version" Style="{StaticResource styleTxtBlk}" />
<TextBox Style="{StaticResource styleTxtBox}" Text="{Binding Mode=OneWay, Path=SWVersionStr }" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="HW Version" Style="{StaticResource styleTxtBlk}" />
<TextBox Style="{StaticResource styleTxtBox}" Text="{Binding Mode=OneWay, Path=HWVersionStr }" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
The code block directly under exists inside a page and is repeated multiple times.
The TextBoxs' were bound to {x:Bind ViewModel.DeviceData.Status.Devices[0].Temperature1 } and so on inside the Page
Now I want to display the same from UserControl.
In the data object, the instance of Devices[] is replaced every few seconds. The new array was created by deserializing from JSON object and directly assigned to like
ViewModel.DeviceData.Status.Devices = FromJSON(string);
Thank you
I agree with #EldHasp. The solution for your scenario is that you might need to create a custom dependency property in the UserControl. Then pass the A.B.C object to this property of the UserControl via binding.
I've made a simple demo about this, you could take look at the code and adjust it for your scenario.
UserControl XAML
<Grid x:Name="MyGrid">
<Grid.Resources>
<Style x:Key="styleTxtBox" TargetType="TextBox">
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Width" Value="75"/>
</Style>
<Style x:Key="styleTxtBlk" TargetType="TextBlock">
<Setter Property="FontSize" Value="12" />
<Setter Property="Margin" Value="10"/>
<Setter Property="Width" Value="75" />
</Style>
</Grid.Resources>
<StackPanel Orientation="Vertical" Margin="20">
<TextBlock Text="{x:Bind Header}" FontSize="16" FontWeight="Bold" Margin="10">
</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Temp1" Style="{StaticResource styleTxtBlk}" />
<TextBox Style="{StaticResource styleTxtBox}" Text="{Binding MyDependencyProperty._List[0].Name}" />
</StackPanel>
</StackPanel>
</Grid>
UserControl Code
public AClass MyDependencyProperty
{
get { return (AClass)GetValue(MyDependencyPropertyProperty); }
set { SetValue(MyDependencyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MyDependencyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyDependencyPropertyProperty =
DependencyProperty.Register("MyDependencyProperty", typeof(AClass), typeof(TestUserControl), new PropertyMetadata(null));
public string Header = "TestHeader";
public TestUserControl()
{
this.InitializeComponent();
this.DataContext = this;
}
MainPage Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Foreground="Red" Text="{x:Bind ViewModel._AClass._List[1].Name}"/>
<local:TestUserControl Grid.Row="1" MyDependencyProperty="{x:Bind ViewModel._AClass}"/>
</Grid>
MainPage Code
public sealed partial class MainPage : Page
{
public TestViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
ViewModel= new TestViewModel();
}
}
public class TestViewModel
{
public AClass _AClass { get; set; }
public TestViewModel()
{
_AClass = new AClass();
}
}
public class AClass
{
public List<BClass> _List { get; set; }
public AClass()
{
_List = new List<BClass>();
_List.Add(new BClass { Name = "123" });
_List.Add(new BClass { Name = "234" });
}
}
public class BClass
{
public string Name { get; set; }
}
And the result:
I have MainWindowViewModel and AccountViewModel.
When I click 'Login' on my MainView:
data about AvaliableExchages is fetched from database.
Property CurrentViewModel is set to AccountViewModel.
AvaliableExchanges property now has 2 strings ("Binance" and "Bitrue"). I can see it in debugger.
The problem is that I can't see them in the View. Combobox that binds its ItemsSource property to AvaliableExchanges Property doesn't show items.
MainViewModel:
internal class MainWindowViewModel : BaseViewModel
{
private List<AppUser> _appUsers = new List<AppUser>();
public List<AppUser> AppUsers
{
get => _appUsers;
set => Set(ref _appUsers, value);
}
public BaseViewModel _currentViewModel;
public BaseViewModel CurrentViewModel
{
get => _currentViewModel;
set => Set(ref _currentViewModel, value);
}
public ICommand LoginCommand { get; }
public bool CanLoginCommandExecute(object p) => true;
public void OnLoginCommandExecuted(object p)
{
if (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password))
{
AppUsers = AppUser.GetAppUserByLoginAndPawwsord(Username, Password);
if (AppUsers.Count > 0)
{
List<string> avaliableExchanges = new List<string>();
foreach(var user in AppUsers)
{
avaliableExchanges.Add(user.Exchange);
}
CurrentViewModel = new AccountViewModel(avaliableExchanges);
LoginViewVisibility = Visibility.Collapsed;
MainMenuVisibility = Visibility.Visible;
}
}
}
public MainWindowViewModel()
{
LoginCommand = new RelayCommand(OnLoginCommandExecuted, CanLoginCommandExecute);
}
}
AccountViewModel:
internal class AccountViewModel : BaseViewModel
{
private ObservableCollection<string> _avaliableExchanges;
public ObservableCollection<string> AvaliableExchanges
{
get => _avaliableExchanges;
set => Set(ref _avaliableExchanges, value);
}
public AccountViewModel()
{
}
public AccountViewModel(List<string> avaliableExchanges)
{
AvaliableExchanges = new ObservableCollection<string>(avaliableExchanges);
}
}
MainWindow:
<Window x:Class="ShababTrade.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ShababTrade"
xmlns:vm="clr-namespace:ShababTrade.ViewModels"
xmlns:views="clr-namespace:ShababTrade.Views"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Title="Shabab Trade" Height="450" Width="800"
WindowState="Maximized" Background="#FF111111">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.Column="0" BorderThickness="0.5" BorderBrush="DarkGray" Margin="5" CornerRadius="5" Visibility="{Binding MainMenuVisibility}">
<StackPanel Orientation="Horizontal" Margin="5">
<Button Style="{StaticResource MainTabStyle}" Background="{Binding AccountTabBackground}" Command="{Binding SelectMainMenuTabCommand}" CommandParameter="Account" Content="Account" />
<Button Style="{StaticResource MainTabStyle}" Background="{Binding TradeHistoryTabBackground}" Command="{Binding SelectMainMenuTabCommand}" CommandParameter="History" Content="Trade History" />
<Button Style="{StaticResource MainTabStyle}" Background="{Binding SpotTabBackground}" Command="{Binding SelectMainMenuTabCommand}" CommandParameter="Spot" Content="Spot" />
<Button Style="{StaticResource MainTabStyle}" Background="{Binding AutoTradingTabBackground}" Command="{Binding SelectMainMenuTabCommand}" CommandParameter="Auto" Content="Auto Trading" Cursor="Hand" />
<Button Style="{StaticResource MainTabStyle}" Background="{Binding FuturesTabBackground}" Command="{Binding SelectMainMenuTabCommand}" CommandParameter="Futures" Content="Futures" Cursor="Hand" />
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="0" BorderThickness="3" CornerRadius="5" Width="350" Height="430"
BorderBrush="{StaticResource PrimaryColorBrush}"
Background="{StaticResource WhiteColorBrush}" Visibility="{Binding LoginViewVisibility}">
<StackPanel>
<TextBlock Margin="0 30 0 0" FontSize="36" TextAlignment="Center" Text="Login"/>
<TextBox Margin="10 50 10 0" FontSize="20" materialDesign:HintAssist.Hint="Username"
Text="{Binding Username}" Cursor="Arrow"/>
<TextBox Margin="10 40 10 0" FontSize="20" materialDesign:HintAssist.Hint="Password"
Text="{Binding Password}" Cursor="Arrow"/>
<Button Margin="10 50 10 0" FontSize="20" Content="Login" Height="40"
Background="{StaticResource SecondaryColorBrush}"
Style="{StaticResource LoginButtonStyle}"
Command="{Binding LoginCommand}" Cursor="Hand"/>
<TextBlock Margin="0 20 0 0" FontSize="16" TextAlignment="Center" >
<Run Text="Not a member?"/>
<Hyperlink Foreground="{StaticResource HyperlinkColorBrush}">Sign in</Hyperlink>
</TextBlock>
</StackPanel>
</Border>
<ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding CurrentViewModel}" Visibility="{Binding MainMenuVisibility}">
</ContentControl>
</Grid>
</Window>
AccountView:
<UserControl x:Class="ShababTrade.Views.AccountView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ShababTrade.Views"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:views="clr-namespace:ShababTrade.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<views:AccountViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.Column="0" BorderThickness="0.5" BorderBrush="DarkGray" Margin="5" CornerRadius="5">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="1 10 0 0">
<TextBlock Text="Exchange:" Foreground="#D81D3C" FontSize="16" Margin="8 0 0 0"/>
<ComboBox Width="100" Height="25" Margin="5 0 0 0" HorizontalContentAlignment="Left" Padding="5 2 5 0"
Background="#2D2D2D" Foreground="White" materialDesign:ColorZoneAssist.Mode="PrimaryDark"
ItemsSource="{Binding AvaliableExchanges, Mode=TwoWay}">
</ComboBox>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Balances}" Foreground="White" >
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}" BasedOn="{StaticResource MaterialDesignFlatButton}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn="{StaticResource MaterialDesignGridViewItem}">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style BasedOn="{StaticResource {x:Type GridViewColumnHeader}}" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridView.ColumnHeaderTemplate>
<DataTemplate>
<TextBlock Foreground="White" Text="{Binding}"/>
</DataTemplate>
</GridView.ColumnHeaderTemplate>
<GridViewColumn Header="Asset" DisplayMemberBinding="{Binding Asset}" Width="100"/>
<GridViewColumn Header="Free" DisplayMemberBinding="{Binding Free, StringFormat=N2}" Width="100"/>
<GridViewColumn Header="Locked" DisplayMemberBinding="{Binding Locked, StringFormat=N2}" Width="100"/>
<GridViewColumn Header="Total" DisplayMemberBinding="{Binding Total, StringFormat=N2}" Width="100"/>
<GridViewColumn Header="RubValue" DisplayMemberBinding="{Binding RubValue, StringFormat=N2}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Border>
</Grid>
</UserControl>
A UserControl must not explicitly set its own DataContext.
Remove
<UserControl.DataContext>
<views:AccountViewModel/>
</UserControl.DataContext>
from the AccountView XAML, because it sets a different AccountViewModel instance than the one the control is expected to operate with.
Also note that it is poinless to set Mode=TwoWay on an ItemsSource Binding.
Image :
<Expander Grid.Row="1" IsExpanded="False" Background="Yellow">
<Grid Background="Yellow">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Filters" />
<CheckBox Grid.Column="1" Content="A"/>
<CheckBox Grid.Column="2" Content="B"/>
<CheckBox Grid.Column="3" Content="C"/>
</Grid>
</Expander>
I want my expander to expand only after a file is loaded. Please help me how to do that. I am using command to the load button.
YourView.xaml
<Window x:Class="WpfSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converter="clr-namespace:WpfSample.Converters"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<converter:IntToBoolConverter x:Key="intToBoolConverter"/>
</Window.Resources>
<Grid>
<Expander Header="Main Expander" IsEnabled="{Binding Items, Converter={StaticResource intToBoolConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsExpanded="{Binding Items, Converter={StaticResource intToBoolConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Background="Yellow">
<ItemsControl x:Name="expanderItem" ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="Sub-Heading">
<ListBox ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch"/>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</Grid>
</Window>
I have created Items as ObservableCollection<string>. You can changed it to your Model.
YourViewModel.cs
private ObservableCollection<string> items;
public ObservableCollection<string> Items
{
get { return items; }
set { items = value; OnPropertyChanged(); }
}
public void LoadExpanderData(object obj)
{
Items = new ObservableCollection<string>();
Items.Add("Item 1");
Items.Add("Item 2");
Items.Add("Item 3");
Items.Add("Item 4");
Items.Add("Item 5");
}
IntToBoolConverter.cs
public class IntToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isExpand = false;
if (value != null && value.GetType().GetProperties().Length > 0)
return isExpand = true;
return isExpand;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return true;
}
}
here is the solution binding a Flag to the property IsEnabled:
for example you set Flag = True when your file is loaded
MainWindow.xaml.cs:
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void ButtonClick(object sender, RoutedEventArgs e)
{
Flag = true;
OnPropertyChanged("Flag");
}
public bool Flag { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
MainWindow.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Expander Grid.Row="0" IsExpanded="False" Background="Yellow" Margin="50,50,200,50" >
<Expander.Style>
<Style>
<Setter Property="Expander.IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Flag}" Value="True">
<Setter Property="Expander.IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<Grid Background="Yellow">
:
:
</Grid>
</Expander>
<Button Grid.Row="1" Click="ButtonClick" Content="Click Me" />
</Grid>
i have played with IsEnabled but you can bind with the property IsExpanded:
<Expander Grid.Row="0" Background="Yellow" Margin="50,50,200,50" >
<Expander.Style>
<Style>
<Setter Property="Expander.IsExpanded" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Flag}" Value="True">
<Setter Property="Expander.IsExpanded" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
even both if you want:
<Expander Grid.Row="0" Background="Yellow" Margin="50,50,200,50" >
<Expander.Style>
<Style>
<Setter Property="Expander.IsEnabled" Value="False" />
<Setter Property="Expander.IsExpanded" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Flag}" Value="True">
<Setter Property="Expander.IsEnabled" Value="True" />
<Setter Property="Expander.IsExpanded" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
here, when you click on button, the Expander becomes Expanded/Enabled
so i trying to create the datagrid out from msdn
but i getting an error and i am not sure why as i copy pastet the whole thing directly from msdn
i getting the error on the
<local:CompleteConverter x:Key="completeConverter" />
<local:Tasks x:Key="tasks" />
that says
it doesn't exists in clr-namespace:DGGroupSortFilterExample
i be using that one in the Example section
CS
namespace DGGroupSortFilterExample
{
public class CompleteConverter : IValueConverter
{
// This converter changes the value of a Tasks Complete status from true/false to a string value of
// "Complete"/"Active" for use in the row group header.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool complete = (bool)value;
if (complete)
return "Complete";
else
return "Active";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string strComplete = (string)value;
if (strComplete == "Complete")
return true;
else
return false;
}
}
public class Task : INotifyPropertyChanged, IEditableObject
{
// The Task class implements INotifyPropertyChanged and IEditableObject
// so that the datagrid can properly respond to changes to the
// data collection and edits made in the DataGrid.
// Private task data.
private string m_ProjectName = string.Empty;
private string m_TaskName = string.Empty;
private DateTime m_DueDate = DateTime.Now;
private bool m_Complete = false;
// Data for undoing canceled edits.
private Task temp_Task = null;
private bool m_Editing = false;
// Public properties.
public string ProjectName
{
get { return this.m_ProjectName; }
set
{
if (value != this.m_ProjectName)
{
this.m_ProjectName = value;
NotifyPropertyChanged("ProjectName");
}
}
}
public class Tasks : ObservableCollection<Task>
{
// Creating the Tasks collection in this way enables data binding from XAML.
}
}
Xaml
<Window x:Class="DGGroupSortFilterExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DGGroupSortFilterExample"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="Group, Sort, and Filter Example" Height="575" Width="525">
<Window.Resources>
<local:CompleteConverter x:Key="completeConverter" />
<local:Tasks x:Key="tasks" />
<CollectionViewSource x:Key="cvsTasks" Source="{StaticResource tasks}"
Filter="CollectionViewSource_Filter">
<CollectionViewSource.SortDescriptions>
<!-- Requires 'xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"' declaration. -->
<scm:SortDescription PropertyName="ProjectName"/>
<scm:SortDescription PropertyName="Complete" />
<scm:SortDescription PropertyName="DueDate" />
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ProjectName"/>
<PropertyGroupDescription PropertyName="Complete"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<DataGrid x:Name="dataGrid1"
ItemsSource="{Binding Source={StaticResource cvsTasks}}"
CanUserAddRows="False">
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<!-- Style for groups under the top level. -->
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<DockPanel Background="LightBlue">
<TextBlock Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Foreground="Blue" Margin="30,0,0,0" Width="100"/>
<TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/>
</DockPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="White" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<TextBlock Text=" Filter completed items " VerticalAlignment="Center" />
<CheckBox x:Name="cbCompleteFilter" VerticalAlignment="Center"
Checked="CompleteFilter_Changed" Unchecked="CompleteFilter_Changed" />
<Button Content="Remove Groups" Margin="10,2,2,2" Click="UngroupButton_Click" />
<Button Content="Group by Project/Status" Margin="2" Click="GroupButton_Click" />
</StackPanel>
</Grid>
</Window>
First, try compiling.. the designer is made aware of new classes only after compilation. if after compilation this isn't resolved- post your entire XAML document, and c# code for that class. Make sure the namespace and using statements are visible.
In order to attempt to get around the problem I outline in Binding Selected RowCount to TextBlock not Firing OnPropertyChanged after DataGrid Scroll; namely updating a TextBlock with the currently selected row count of a DataGrid when it scrolls. I have attempted to introduce the AttachedCommandBehaviour designed by Marlon Grech [an amazing class structure that allows you to bind commands to specific events of a given control].
Now for the question, using this AttachedCommandBehaviour, how can I get a TextBlock to update based upon a DataGrid's SelectionChangedProperty?
The XMAL is
<Window x:Class="ResourceStudio.Views.AddCultureWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:ResourceStudio.ViewModels"
xmlns:converters="clr-namespace:ResourceStudio.Converters"
xmlns:dataAccess="clr-namespace:ResourceStudio.DataAccess"
xmlns:attachedCommand="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
Title="Add Culture"
Height="510" Width="400"
WindowStartupLocation="CenterOwner"
VerticalContentAlignment="Stretch"
MinWidth="380" MinHeight="295">
<DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="377*"/>
<ColumnDefinition Width="15*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="1">
<Grid>
<Grid.Resources>
<converters:EnumToBooleanConverter x:Key="enumToBooleanConverter"/>
<Style x:Key="HyperlinkButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Header="Filters" Grid.Row="0" Margin="0,0,0,5">
<StackPanel VerticalAlignment="Top">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="2,2,2,2">
<RadioButton Content="All Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=AllCultures}"/>
<RadioButton Content="Neutral Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=NeutralCultures}"/>
<RadioButton Content="Specific Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=SpecificCultures}"/>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Language:" Grid.Column="0"/>
<TextBox HorizontalAlignment="Stretch" Grid.Column="1"
Margin="2,0,2,0" Height="22"/>
</Grid>
</StackPanel>
</GroupBox>
<DataGrid x:Name="cultureDataGrid" Grid.Row="1" AlternatingRowBackground="Gainsboro" AlternationCount="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AutoGenerateColumns="False" RowHeaderWidth="0" IsReadOnly="True"
CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Extended"
EnableRowVirtualization="True" EnableColumnVirtualization="True"
ItemsSource="{Binding Cultures, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
<DataGrid.Columns>
<DataGridTextColumn Header="Code" Binding="{Binding Code}" IsReadOnly="True"/>
<DataGridTextColumn Header="Language" Binding="{Binding Language}" IsReadOnly="True"/>
<DataGridTextColumn Header="LocalName" Binding="{Binding LocalName}" IsReadOnly="True"/>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#FF007ACC"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, IsAsync=True}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
<StackPanel Grid.Row="2" HorizontalAlignment="Right">
<Button Name="button1" Style="{StaticResource HyperlinkButton}"
Focusable="False">
<TextBlock>
<Hyperlink Focusable="False">
Select All
</Hyperlink>
</TextBlock>
</Button>
</StackPanel>
<GroupBox Grid.Row="3" Header="Options">
<CheckBox Content="Copy default values" Margin="3,3"/>
</GroupBox>
<StackPanel Grid.Row="4" Orientation="Horizontal"
HorizontalAlignment="Right" Margin="0,2,0,2">
<Button Content="Select" Width="70" Height="25"
Margin="0,5,5,5" HorizontalAlignment="Right"
VerticalContentAlignment="Center" IsDefault="True"/>
<Button Content="Cancel" Width="70" Height="25"
Margin="5,5,0,5" HorizontalAlignment="Right"
VerticalContentAlignment="Center" IsCancel="True"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
<StatusBar Grid.Row="1" Margin="0,0.4,0.4,-0.4">
<StatusBarItem DockPanel.Dock="Left" Background="#FF007ACC" Margin="0,2,0,0">
<TextBlock Text="{Binding TotalSelectedCultures}" Margin="5,0,0,0" Foreground="White"/>
</StatusBarItem>
</StatusBar>
</Grid>
</DockPanel>
</Window>
My ViewModel is
public class CultureDataViewModel : ViewModelBase
{
public FilterType SelectedFilterType { get; private set; }
public ICollectionView CulturesView { get; private set; }
public MultiSelectCollectionView<CultureViewModel> Cultures { get; private set; }
public CultureDataViewModel()
{
SelectedFilterType = FilterType.AllCultures;
LoadCultures();
}
void OnCultureViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
string IsSelected = "IsSelected";
(sender as CultureViewModel).VerifyPropertyName(IsSelected);
if (e.PropertyName == IsSelected)
this.OnPropertyChanged("TotalSelectedCultures");
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (CultureViewModel cultVm in e.NewItems)
cultVm.PropertyChanged += this.OnCultureViewModelPropertyChanged;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (CultureViewModel cultVm in e.OldItems)
cultVm.PropertyChanged -= this.OnCultureViewModelPropertyChanged;
}
public void LoadCultures()
{
// Fill the Culutres collection...
}
public string TotalSelectedCultures
{
get
{
int selectedCultures = this.Cultures.SelectedItems.Count;
return String.Format("{0:n0} of {1:n0} cultures selected",
selectedCultures,
Cultures.Count);
}
}
}
The link to a downloadable example of the use of the AttachedCommandBehaviour is Here. Your help is most appreciated...
So, as far as I understand, you want to have TextBlock which says:
"5 of 100 items selected"
I've reconstructed your window, the button below the list shows how many items you have selected.
Is that correct? If that is really all you want to do, you don't need to involve the ViewModel at all:
All I did, was this:
<Button Content="{Binding SelectedItems.Count, ElementName=cultureDataGrid}" />
Does this help in some way already or is there a reason for using the pretty complex method with event to command etc.?
EDIT
Your problem basically melts down to "How can I bind to SelectedItems property of a DataGrid. Here is how I solved it:
Define a behavior with a dependency property for the DataGrid, which relays the SelectedItems of the DataGrid to the DependencyProperty:
public class BindableSelectedItems : Behavior<DataGrid>
{
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof (IList), typeof (BindableSelectedItems), new PropertyMetadata(default(IList), OnSelectedItemsChanged));
private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var grid = ((BindableSelectedItems) sender).AssociatedObject;
if (grid == null) return;
// Add logic to select items in grid
}
public IList SelectedItems
{
get { return (IList) GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var grid = (DataGrid) sender;
SelectedItems = grid.SelectedItems;
}
}
Attach this behavior to the DataGrid and bind the SelectedItems property to a property on your ViewModel:
<DataGrid x:Name="cultureDataGrid" ItemsSource="{Binding Cultures}">
<i:Interaction.Behaviors>
<behaviors:BindableSelectedItems x:Name="CulturesSelection"
SelectedItems="{Binding SelectedCultures, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
with the property on your ViewModel like this:
public IList SelectedCultures
{
get { return _selectedCultures; }
set
{
_selectedCultures = value;
OnPropertyChanged("SelectedCultures");
}
}
If you only want to get the count of selected items, you can bind to the behavior directly and don't need a field on the ViewModel for this:
<TextBlock Text="{Binding Path=SelectedItems.Count, ElementName=CulturesSelection}" Margin="5,0,0,0" Foreground="White"/>
In your ViewModel you can use the SelectedItems property to work with the selection:
var selectedCultures= SelectedCultures.OfType<CultureViewModel>();
I've uploaded the solution, see the link in the comments.
I hope this helps and whish you good luck! There always are a thousand ways to do stuff in WPF and it is really hard to find the best one sometimes...