I have a button with the following content:
<StackPanel Orientation="Horizontal">
<TextBlock Text="Connect"/>
<materialDesign:PackIcon Kind="Arrow"/>
</StackPanel>
I searched and found this: WPF Button content binding but I'm not sure how to apply the solution when I have all of the three: a Stackpanel, the PackIcon (object) and the Textblock.
I have this progressBar which I make it appear under the button:
<ProgressBar x:Name="XZ" Foreground="Black" Grid.Row="4" Grid.Column="1"
Visibility="{Binding Connecting, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Value="50"
IsIndeterminate="True" />
I want to make it so when I click the button, instead of showing the ProgressBar where it is right now, to basically remove the Text and the PackIcon and place the ProgressBar in the button.
Actually changing in the controls could be done with Data Triggers; though that seems a bit over the top in this case.
I would just toggle the visibility of two controls:
<Grid>
<StackPanel Orientation="Horizontal" Visibility="{Binding Connecting, Converter={StaticResource BooleanToCollapsedConverter}}"">
<TextBlock Text="Connect"/>
<materialDesign:PackIcon Kind="Arrow"/>
</StackPanel>
<ProgressBar x:Name="XZ" Foreground="Black" Grid.Row="4" Grid.Column="1"
Visibility="{Binding Connecting, Converter={StaticResource BooleanToVisibilityConverter}}"
Value="50"
IsIndeterminate="True" />
</Grid>
That would be the content of your button. BooleanToCollapsedConverter is just the inverse of a VisibiltyToBooleanConverter; there are a number of ways to do it and is left as an exercise.
As an aside; UpdateSourceTrigger doesn't make any sense on a OneWay binding (it doesn't update the source!) and you don't even need that on visibility as that's not an input the user can change.
You could use a data template. Something like:
XAML:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ButtonInfo}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Press me"></Label>
<Label Grid.Row="1" Content="{Binding Label}"></Label>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProgressInfo}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ProgressBar Height="30" Value="{Binding Progress}"></ProgressBar>
<Label Grid.Row="1" Content="{Binding Label}"></Label>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Button Command="{Binding ProcessCommand}" Content="{Binding ButtonInfo}">
</Button>
</Grid>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
public class ViewModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainWindowViewModel:ViewModelBase
{
public MainWindowViewModel()
{
ButtonInfo = new ButtonInfo(){Label = "Button Info"};
ProcessCommand = new DelegateCommand(Process);
}
private ButtonInfo _buttonInfo;
public ButtonInfo ButtonInfo
{
get { return _buttonInfo; }
set
{
_buttonInfo = value;
OnPropertyChanged();
}
}
public DelegateCommand ProcessCommand { get; set; }
private async void Process()
{
ButtonInfo = new ProgressInfo(){Label = "Progress Info"};
await ProcessAsync();
}
private Task ProcessAsync()
{
return Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Application.Current.Dispatcher.Invoke(() =>
{
ButtonInfo.Progress = i;
if (i==99)
{
ButtonInfo = new ButtonInfo(){Label = "Button Again"};
}
});
Thread.Sleep(100);
}
});
}
}
public class ButtonInfo:ViewModelBase
{
private string _label;
private int _progress;
private bool _isProcessing;
public string Label
{
get { return _label; }
set
{
_label = value;
OnPropertyChanged();
}
}
public int Progress
{
get { return _progress; }
set
{
_progress = value;
OnPropertyChanged();
}
}
public bool IsProcessing
{
get { return _isProcessing; }
set
{
_isProcessing = value;
OnPropertyChanged();
}
}
}
public class ProgressInfo : ButtonInfo { }
You can create a template for the button to achieve this, then reuse the template everywhere you want a button with loading as following:
<Button Width="120" Height="40" Tag="False" Name="loadingButton" Click="loadingButton_Click">
<Button.Template>
<ControlTemplate>
<Border Name="PART_Border" BorderBrush="Black" BorderThickness="1" CornerRadius="2" Background="Transparent">
<Grid Name="PART_Root">
<TextBlock Name="PART_Text" HorizontalAlignment="Center" VerticalAlignment="Center">Data</TextBlock>
<ProgressBar IsIndeterminate="True" Name="PART_Loading"></ProgressBar>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Tag" Value="True">
<Setter TargetName="PART_Text" Property="Visibility" Value="Collapsed"></Setter>
<Setter TargetName="PART_Loading" Property="Visibility" Value="Visible"></Setter>
</Trigger>
<Trigger Property="Tag" Value="False" >
<Setter TargetName="PART_Text" Property="Visibility" Value="Visible"></Setter>
<Setter TargetName="PART_Loading" Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
And the event for button click would be:
private async void loadingButton_Click(object sender, RoutedEventArgs e)
{
loadingButton.Tag = true.ToString();//display loading
await Task.Run(() => { Thread.Sleep(4000); });//fake data loading
loadingButton.Tag = false.ToString();//hide loading
}
Note that you can also bind the Tag property for a property inside your view model if you where using MVVM pattern.
Related
I have a WPF Application in net core. I use prism and mahapps.
In MainWindow I have a Hamburger menu where I add HamburgerMenuGlyphItem via regions.
In VM for HamburgerMenuGlyphItemView, I set a label property but it doesn't work. The label property doesn't update on MenuItemControl. When I check the DataContext for the DeclarationMenuItemView in LiveVisualTree it says "DeclarationMenuItemView" not the ViewModel...
Here is the MainWindow with DataTemplate For HamburgerMenuItem
<mah:MetroWindow x:Class="PrismJPKEditor.Views.MainWindow"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:viewModels="clr-namespace:PrismJPKEditor.Modules.JPK.ViewModels;assembly=PrismJPKEditor.Modules.JPK"
xmlns:core="clr-namespace:PrismJPKEditor.Core;assembly=PrismJPKEditor.Core"
Title="{Binding Title}" Height="350" Width="525" >
<DockPanel>
<DockPanel.Resources>
<DataTemplate x:Key="MenuItemTemplate" DataType="{x:Type viewModels:MenuItemViewModel}">
<Grid x:Name="RootGrid"
Height="48"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mah:HamburgerMenu}}, Path=CompactPaneLength}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{Binding Icon}"
Focusable="False" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding Label}"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type mah:HamburgerMenu}}, Path=IsPaneOpen}" Value="False">
<Setter TargetName="RootGrid" Property="ToolTip" Value="{Binding ToolTip, Mode=OneWay}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DockPanel.Resources>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Plik">
<MenuItem Header="Otwórz"/>
<MenuItem Header="Zapisz"/>
<MenuItem Header="Zapisz jako"/>
<MenuItem Header="Zamknij"/>
</MenuItem>
</Menu>
<mah:HamburgerMenu DockPanel.Dock="Left" ItemTemplate="{StaticResource MenuItemTemplate}" OptionsItemTemplate="{StaticResource MenuItemTemplate}">
<mah:HamburgerMenu.ItemsSource>
<mah:HamburgerMenuItemCollection prism:RegionManager.RegionName="{x:Static core:RegionNames.HamburgerMenuRegion}"/>
</mah:HamburgerMenu.ItemsSource>
</mah:HamburgerMenu>
<!--<mah:HamburgerMenu DockPanel.Dock="Left" ItemTemplate="{StaticResource MenuItemTemplate}" prism:RegionManager.RegionName="{x:Static core:RegionNames.HamburgerMenuRegion}">
</mah:HamburgerMenu>-->
<ContentControl prism:RegionManager.RegionName="{x:Static core:RegionNames.ContentRegion}"/>
</DockPanel>
</mah:MetroWindow>
Here is the MenuItemViewModel from MenuItemTemplate
public class MenuItemViewModel : BindableBase, IHamburgerMenuItemBase
{
private object _icon;
private object _label;
private object _toolTip;
private bool _isVisible = true;
public object Icon
{
get => _icon;
set => SetProperty(ref _icon, value);
}
public object Label
{
get => _label;
set => SetProperty(ref _label, value);
}
public object ToolTip
{
get => _toolTip;
set => SetProperty(ref _toolTip, value);
}
public bool IsVisible
{
get => _isVisible;
set => SetProperty(ref _isVisible, value);
}
}
And here is the ViewModel for MenuItemView when I initialize the Label property which should be working
public class DeclarationMenuItemViewModel : MenuItemViewModel
{
public DeclarationMenuItemViewModel()
{
Label = "Deklaracja";
}
}
And here is the MenuItemView
<mah:HamburgerMenuGlyphItem x:Class="PrismJPKEditor.Modules.JPK.HamburgerMenuItems.DeclarationMenuItemView"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
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:PrismJPKEditor.Modules.JPK.HamburgerMenuItems"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
>
</mah:HamburgerMenuGlyphItem>
Here is the Region adapter for the HamburgermenuItemCollection
public HamburgerMenuItemCollectionRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory) : base(regionBehaviorFactory)
{
}
protected override void Adapt(IRegion region, HamburgerMenuItemCollection regionTarget)
{
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (HamburgerMenuGlyphItem element in e.NewItems)
{
regionTarget.Add(element);
}
}
};
}
protected override IRegion CreateRegion()
{
return new SingleActiveRegion();
}
}
Because the View DeclarationMenuItemView isn't in the Views folder, I have set
public void RegisterTypes(IContainerRegistry containerRegistry)
{
ViewModelLocationProvider.Register<DeclarationMenuItemView, DeclarationMenuItemViewModel>();
}
Here is the Module class
public class JPKModule : IModule
{
private readonly IRegionManager _regionManager;
public JPKModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.HamburgerMenuRegion, typeof(HamburgerMenuItems.DeclarationMenuItemView));
//_regionManager.RegisterViewWithRegion(RegionNames.HamburgerMenuRegion, typeof(HamburgerMenuItems.SellMenuItem));
//_regionManager.RegisterViewWithRegion(RegionNames.HamburgerMenuRegion, typeof(HamburgerMenuItems.BuyMenuItem));
//_regionManager.RegisterViewWithRegion(RegionNames.HamburgerMenuRegion, typeof(TestView));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
ViewModelLocationProvider.Register<DeclarationMenuItemView, DeclarationMenuItemViewModel>();
}
}
For some testing I changed the DataTemplate to
<DataTemplate x:Key="HamburgerMenuItem" DataType="{x:Type mah:HamburgerMenuGlyphItem}">
<DockPanel Height="48" LastChildFill="True">
<Grid x:Name="IconPart"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type mah:HamburgerMenu}}, Path=CompactPaneLength}"
DockPanel.Dock="Left">
<Image Margin="12"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="{Binding Glyph}" />
</Grid>
<TextBlock x:Name="TextPart"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding Label}" />
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type mah:HamburgerMenu}}, Path=PanePlacement}" Value="Right">
<Setter TargetName="IconPart" Property="DockPanel.Dock" Value="Right" />
<Setter TargetName="TextPart" Property="Margin" Value="8 0 0 0" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
And in the cs file of the View I set
public partial class DeclarationMenuItem : HamburgerMenuGlyphItem
{
public DeclarationMenuItem()
{
InitializeComponent();
Glyph = "M";
Label = "Test";
}
}
And from here Label is working ..
And when I add to the xaml file of the view the line
Label = "{Binding Label"}
I get following error:
Error 1 MainWindowViewModel Label DeclarationMenuItemView.Label String Label property not found on object of type MainWindowViewModel.
But VM for DeclarationMenuItem is DeclarationMenuItemViewModel not the
MainWindowViewModel
I created the Login Entities (Login.cs) and also I created Logins property of type DbSet of Login to save logins with (SubBVDbContext.cs) as public DbSet<Login> Logins { get; set;}, then I seed entries into the Login table in the (Configuration.cs) in Migration Folder. After that, I created the (LoginPageViewModel.cs) which includes LoginWrapper that wraps up all entities, as well as the (NavigationView.xaml) that allows navigation from model to another. I have already created (MainViewModel.cs), and the MainWindow.xaml that contain the views of the models and the navigations between them.
What I want is to display the LoginPage first then show the other views.
Login.cs
public class Login
{
public int Id { get; set; }
[Required]
[StringLength(50)]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
LoginDetailView.xaml(UserControl)
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Login.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<PasswordBox PasswordChar="{Binding Login.Password, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<Button Width="100" ToolTip="Login to Subcontractors" HorizontalAlignment="Center" Content="login"/>
NavigationViewModel.xaml (UserControl)
<UserControl.Resources>
<Style x:Key="NaviItemContainerStyle" TargetType="ContentPresenter">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Margin" Value="2"/>
</Style>
<DataTemplate x:Key="NaviItemTemplate">
<Button Content="{Binding DisplayMember}"
Command="{Binding OpenDetailViewCommand}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid x:Name="grid">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Foreground" Value="DarkRed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
<RowDefinition/>
</Grid.RowDefinitions>
<GroupBox Header="Subcontarctors">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Subs}"
ItemContainerStyle="{StaticResource NaviItemContainerStyle}"
ItemTemplate="{StaticResource NaviItemTemplate}"/>
</ScrollViewer>
</GroupBox>
<GroupBox Header="POs" Grid.Row="1" >
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto" >
<ItemsControl ItemsSource="{Binding SubPOs}"
ItemContainerStyle="{StaticResource NaviItemContainerStyle}"
ItemTemplate="{StaticResource NaviItemTemplate}"/>
</ScrollViewer>
</GroupBox>
</Grid>
LoginPageViewModel.cs
public class LoginPageViewModel:ViewModelBase
{
private LoginWrapper _login;
private IMessageDialogService _messageDialogService;
public LoginPageViewModel(IMessageDialogService messageDialogService)
{
_messageDialogService = messageDialogService;
}
public LoginWrapper Login
{
get { return _login; }
private set
{
_login = value;
OnPropertyChanged();
}
}
}
LoginWrapper.cs
public class LoginWrapper : ModelWrapper<Login>
{
public LoginWrapper(Login model) : base(model)
{
}
public int Id { get { return Model.Id; } }
public string Email
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
public string Password
{
get { return GetValue<string>(); }
set { SetValue(value); }
}
}
App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
var bootstrapper = new Bootstrapper();
var container = bootstrapper.Bootstrap();
var mainWindow = container.Resolve<MainWindow>();
mainWindow.Show();
}
Note: in the Bootstrapper there is a container that contains the whole models and the views For Example:
public class Bootstrapper
{
public IContainer Bootstrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<EventAggregator>().As<IEventAggregator>().SingleInstance();
builder.RegisterType<SubBVDbContext>().AsSelf();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<SubPODetailViewModel>().As<ISubPODetailViewModel>();
builder.RegisterType<LoginPageViewModel>.AsSelf();
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:SubDetailViewModel}">
<view:SubDetailView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:SubPODetailViewModel}">
<view:SubPODetailView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:LoginPageViewModel}">
<view:LoginDetailView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Menu Grid.ColumnSpan="2" FontSize="13">
<MenuItem Header="Create">
<MenuItem Header="New Subcontractor" Command="{Binding CreateNewDetailCommand}"
CommandParameter="{x:Type viewModel:SubDetailViewModel}"/>
<MenuItem Header="New P.O." Command="{Binding CreateNewDetailCommand}"
CommandParameter="{x:Type viewModel:SubPODetailViewModel}"/>
</MenuItem>
</Menu>
<view:NavigationView Grid.Row="1" DataContext="{Binding NavigationViewModel}"/>
<ContentControl Grid.Row="1" Grid.Column="1" Content="{Binding DetailViewModel}"/>
</Grid>
The Model view 1,The Model view 2
The SolutionExplorer Folders
The Link for My WPF App: http://www.filefactory.com/file/4of3jrkspmog/SubBvApp.zip
Have style for a series of buttons btn1, btn2, btn3, etc.
Inside the style for each button is a TextBlock for displaying the "Content" of the button, (since the border inside the style covers any content of the button itself).
Now, I would like for the TextBlock name to be tied to the button name. For example - btn1's text block's name would be btn1Txt. The purpose of this would be the end user can assign each button its own text in a settings menu.
Any hints on how I would go about this? I admit I'm relatively new to WPF and bindings.
EDIT:::: WHAT I"VE GOT SO FAR THAT IS WORKING.
On load, the program checks the settings file for the Text for each button. Each button's content is assigned the proper information. Then inside the style, I bind the TextBlock Text to the content of the parent button.
This may not be the normal way of going about it, but it works
Method
List<string> MainButtons = Properties.Settings.Default.MainButtonNames.Cast<string>().ToList();
for (int i = 0; i < MainButtons.Count(); i++)
{
string actualNum = Convert.ToString((i + 1));
var MainButtonFinder = (Button)this.FindName("MainButton" + actualNum);
Console.WriteLine(MainButtonFinder.Name);
MainButtonFinder.Content = MainButtons[i];
Console.WriteLine(MainButtonFinder.Content);
}
Style
<Style x:Key="MainButtonStyle" TargetType="{x:Type Button}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Width" Value="100px"/>
<Setter Property="MinHeight" Value="50"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="20" Height="45" Width="100" Margin="0" Background="#FF99CCFF">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="LCARS" Foreground="White" Padding="5px" FontSize="18px" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>`
This is the wrong way to go about what you're trying to do. Here's the "right" way to do it. There's a fair amount of boilerplate code here, but you get used to it.
Write a button viewmodel and give your main viewmodel an ObservableCollection of those:
#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
#endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class
#region MainViewModel Class
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
ButtonItems.Add(new ButtonItemViewModel("First Command", "First Item", () => MessageBox.Show("First Item Executed")));
ButtonItems.Add(new ButtonItemViewModel("Second Command", "Second Item", () => MessageBox.Show("Second Item Executed")));
}
#region ButtonItems Property
public ObservableCollection<ButtonItemViewModel> ButtonItems { get; }
= new ObservableCollection<ButtonItemViewModel>();
#endregion ButtonItems Property
}
#endregion MainViewModel Class
#region ButtonItemViewModel Class
public class ButtonItemViewModel : ViewModelBase
{
public ButtonItemViewModel(String cmdName, String text, Action cmdAction)
{
CommandName = cmdName;
Text = text;
Command = new DelegateCommand(cmdAction);
}
#region Text Property
private String _text = default(String);
public String Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
OnPropertyChanged();
}
}
}
#endregion Text Property
#region CommandName Property
private String _commandName = default(String);
public String CommandName
{
get { return _commandName; }
private set
{
if (value != _commandName)
{
_commandName = value;
OnPropertyChanged();
}
}
}
#endregion CommandName Property
public ICommand Command { get; private set; }
}
#endregion ButtonItemViewModel Class
public class DelegateCommand : ICommand
{
public DelegateCommand(Action action)
{
_action = action;
}
private Action _action;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action?.Invoke();
}
}
Make that MainViewModel the DataContext of your Window:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
And here's how you can put it all together in the XAML:
<Grid>
<StackPanel Orientation="Vertical">
<GroupBox Header="Buttons">
<ItemsControl ItemsSource="{Binding ButtonItems}" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Margin="2"
MinWidth="80"
Content="{Binding Text}"
Command="{Binding Command}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
<GroupBox Header="Edit Buttons">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
Margin="2"
x:Name="ButtonEditorListBox"
ItemsSource="{Binding ButtonItems}"
HorizontalAlignment="Left"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="2"
Text="{Binding CommandName}"
/>
<TextBlock
Margin="2"
Text="{Binding Text, StringFormat=': "{0}"'}"
/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<ContentControl
Grid.Column="1"
Margin="8,2,2,2"
Content="{Binding SelectedItem, ElementName=ButtonEditorListBox}"
>
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock
Margin="2"
HorizontalAlignment="Stretch"
FontWeight="Bold"
Text="{Binding CommandName, StringFormat={}{0}: }"
/>
<TextBox
Margin="2"
HorizontalAlignment="Stretch"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
/>
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</GroupBox>
</StackPanel>
</Grid>
Back to your question:
Inside the style for each button is a TextBlock for displaying the "Content" of the button, (since the border inside the style covers any content of the button itself).
You're doing styles wrong. Very, very wrong. I can help you fix it if you show me the style.
As I understand what you want is to modify the "text" of the button, it occurs to me that you can do it this way.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox Text="{Binding ElementName=ButtonTest, Path=Content, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
<Button Name="ButtonTest" Grid.Row="1" Width="100" Height="40">
<Button.ContentTemplate>
<DataTemplate>
<TextBlock Foreground="Blue" Text="{Binding}"></TextBlock>
</DataTemplate>
</Button.ContentTemplate>
</Button>
</Grid>
I am currently struggling to get something to work, which in my head does not seem to be that hard.
A Got a TopLevel User Control which is displayed in a Window:
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:PCodeViewModel}">
<controls:PCodeTabControl />
</DataTemplate>
<DataTemplate x:Key="TabItemHeaderTemplate">
<TextBlock FontWeight="Medium" Text="{Binding}" />
</DataTemplate>
<Style x:Key="TabItemStyle" TargetType="{x:Type dx:DXTabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="Content" Value="{Binding}" />
</Style>
<DataTemplate DataType="{x:Type viewModels:MexCompileViewModel}">
<controls:MexCompileTabControl />
</DataTemplate>
</UserControl.Resources>
<Grid>
<dx:DXTabControl ItemContainerStyle="{StaticResource TabItemStyle}"
ItemHeaderTemplate="{StaticResource TabItemHeaderTemplate}"
ItemsSource="{Binding Tabs}" />
</Grid>
The corresponding ViewModel is here:
private ICommand createNewProjectCommand;
private string sandboxRoot;
public MatlabBuildViewModel()
{
this.Init();
}
public void Init()
{
this.InitTabs();
}
public void InitTabs()
{
this.Tabs = new ObservableCollection<TabViewModelBase>
{
new MexCompileViewModel(),
new PCodeViewModel()
};
this.SandboxRoot = #"E:\_SupportTools\CaseManager";
}
public ObservableCollection<TabViewModelBase> Tabs { get; private set; }
public void NotifyChildren()
{
Messenger.Default.Send(new SandboxRootUpdated());
}
public string SandboxRoot
{
get
{
return this.sandboxRoot;
}
set
{
if (value != null)
{
this.sandboxRoot = value;
this.OnPropertyChanged();
this.NotifyChildren();
}
}
}
/// <summary>
/// Gets the create new project command.
/// </summary>
public ICommand CreateEmptyProjectCommand
{
get
{
if (this.createNewProjectCommand == null)
{
this.createNewProjectCommand = new DelegateCommand(Debugger.Break);
}
return this.createNewProjectCommand;
}
}
Now as you can see I am displaying two tabs by having a DataTemplate for the targetType MexCompileViewModel and PCodeViewModel.
Both userControls bound by a Dattemplate share a common UserControl which contains a number of buttons.
Here is the MexCompileTabControl as Example
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<compositeControls:MexCompileGrid Grid.Column="0" IsEnabled="{Binding IsEnabled}" />
<StackPanel Grid.Column="1">
<compositeControls:CommonActionsControl />
</StackPanel>
</Grid>
The CommonActionsControl is just a StackPanel with Buttons:
<StackPanel helpers:MarginSetter.Margin="3">
<GroupBox Header="MatlabProject-File">
<StackPanel helpers:MarginSetter.Margin="3">
<Button Command="{Binding CreateEmptyProjectCommand}" Content="Create empty project-file" />
<Button Content="Refresh" />
</StackPanel>
</GroupBox>
<GroupBox Header="Actions">
<StackPanel helpers:MarginSetter.Margin="3">
<Button Content="Clean" />
<Button Content="Rebuild" />
<Button Content="Generate m-Script" />
</StackPanel>
</GroupBox>
</StackPanel>
Code Behind:
public CommonActionsControl()
{
this.InitializeComponent();
}
public static readonly DependencyProperty CreateEmptyProjectCommandProperty = DependencyProperty.Register("CreateEmptyProjectCommand", typeof(ICommand), typeof(CommonActionsControl), new PropertyMetadata(default(ICommand)));
public ICommand CreateEmptyProjectCommand
{
get
{
return (ICommand)GetValue(CreateEmptyProjectCommandProperty);
}
set
{
this.SetValue(CreateEmptyProjectCommandProperty, value);
}
}
So what I am trying to achieve is:
My Commands are defined in the TopLevelViewModel. Now I want my CommonActionsControl inherit these Commands since the Control should be used multiple times. Can you help me with this?
Since CommonActionsControl displays common actions defined in the TopLevelViewModel, it would make sense to have them as part of the TopLevelView instead of displaying them on every tab.
If you really do want them on every tab, then I say your best bet is to add the commands to your TabViewModelBase so that the various tab views can bind to them. You're still free to implement them once in TopLevelViewModel, and just pass them into the various tab VMs by injecting them in the constructor.
I've got my own user control:
[TemplateVisualState(Name = StateValid, GroupName = GroupValidation)]
[TemplateVisualState(Name = StateInvalidFocused, GroupName = GroupValidation)]
[TemplateVisualState(Name = StateInvalidUnfocused, GroupName = GroupValidation)]
public class SearchTextBoxControl : TextBox
{
// properties removed for brevity
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.BindingValidationError += (s, e) => UpdateValidationState();
this.UpdateValidationState();
}
public const string GroupValidation = "ValidationStates";
public const string StateValid = "Valid";
public const string StateInvalidFocused = "InvalidFocused";
public const string StateInvalidUnfocused = "InvalidUnfocused";
private void UpdateValidationState()
{
var textBox = this.GetTemplateChild("ContentTextBox");
if (textBox != null)
{
VisualStateManager
.GoToState(textBox as Control,
Validation.GetErrors(this).Any() ?
StateInvalidUnfocused :
StateValid,
true);
}
}
}
and XAML:
<Style TargetType="local:SearchTextBoxControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SearchTextBoxControl">
<Grid Grid.Column="1"
Grid.ColumnSpan="3"
Grid.Row="1"
Margin="{TemplateBinding Margin}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<TextBox x:Name="ContentTextBox"
Grid.ColumnSpan="2"
IsReadOnly="{TemplateBinding IsReadOnly}"
Text="{TemplateBinding Text}">
</TextBox>
<Button Grid.Column="1"
Style="{StaticResource BrowseButton}"
Command="{TemplateBinding Command}">
<ToolTipService.ToolTip>
<ToolTip Content="{TemplateBinding ToolTip}" />
</ToolTipService.ToolTip>
<Image Source="../../Resources/Images/magnifier.png"
Style="{StaticResource BrowseButtonImage}" />
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
What should I do to pass validation errors to the TextBox x:Name="ContentTextBox" validation service (I want the same validation error tooltip on my control text box)?
Kind regards!
You can implement IDataErrorInfo Interface. It is available in ComponentModel Lib.
using System.ComponentModel;
public class SearchTextBoxControl : TextBox , IDataErrorInfo
{
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get { throw new NotImplementedException(); }
}
#endregion
// Your Code
}