WPF Prism Binding in HamburgerMenu via regions doesn't work - c#

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

Related

WPF binding to UserControl from Page

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:

How to display the LoginPage before viewing the program?

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

Binding WPF Control Name to different Control

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>

Create MenuItems from Collection

i tried to create MenuItems of collection items - and failed. In detail: I have a simple class ClassA that defines a string-property 'HeadText'.
In my MainViewModel i defined an ObservableCollection property. The collection is filled with 3 items. Now in XAML i want to create MenuItems of these 3 items of type ClassA. I did the following:
<Window.Resources>
<CompositeCollection x:Key="CollA">
<ItemsControl ItemsSource="{Binding Path=MItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding HeadText}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</CompositeCollection>
</Window.Resources>
<Grid>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Source={StaticResource CollA}}"/>
</Grid>
But all i get is an empty menu bar. Any ideas how i can do this?
The viewmodel and the class ClassA:
public class MainVM
{
public MainVM() {
_mItems.Add(new ClassA() { HeadText = "A" });
_mItems.Add(new ClassA() { HeadText = "B" });
_mItems.Add(new ClassA() { HeadText = "C" });
}
private ObservableCollection<ClassA> _mItems = new ObservableCollection<ClassA>();
public ObservableCollection<ClassA> MItems{
get { return _mItems; }
}
}
public class ClassA
{
public ClassA() { }
public String HeadText { get; set; }
}
Thanks in advance.
Edit:
If i write this, it works:
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MItems}">
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Header" Value="{Binding HeadText}"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>
But i want to do it the other way. And i'm interested why the other way does not work.
I found the answer here. My XAML first looked like this:
<Window.DataContext>
<local:MainVM/>
</Window.DataContext>
<Window.Resources>
<CollectionViewSource Source="{Binding Path=MItems}" x:Key="source"/>
</Window.Resources>
<StackPanel>
<Menu DockPanel.Dock="Top">
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding HeadText}"/>
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemsSource>
<CompositeCollection>
<MenuItem Header="Static 1"/>
<MenuItem Header="Static 2"/>
<CollectionContainer Collection="{Binding Source={StaticResource source}}"/>
<MenuItem Header="Static 3"/>
</CompositeCollection>
</Menu.ItemsSource>
</Menu>
</StackPanel>
And the MainVM.cs and ClassA:
public class MainVM
{
public MainVM() {
_mItems.Add(new ClassA() { HeadText = "Dyn1" });
_mItems.Add(new ClassA() { HeadText = "Dyn2" });
_mItems.Add(new ClassA() { HeadText = "Dyn3" });
}
private ObservableCollection<ClassA> _mItems = new ObservableCollection<ClassA>();
public ObservableCollection<ClassA> MItems{
get { return _mItems; }
}
}
public class ClassA
{
public ClassA() { }
private string _text = "";
public String HeadText {
get { return _text; }
set { _text = value; }
}
}
This works fine but you have to notice that Visual Studio will give you BindingExpression errors in the case you really mix static and dynamic menu items.
The reason for this is that the XAML parser applies the specified ItemContainerStyle to the dynamic AND to the static menu items: it creates a new MenuItem and binds the HeadText property to the Header property of the newly created MenuItem. This is done for the dynamic menu items as well as for the static ones. Because there is no HeadText property in the MenuItem class, an error is displayed.
The application will not crash, but it's not a good solution. A workaround for this is to split the MenuBar like this:
<StackPanel>
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Menu Grid.Column="0">
<MenuItem Header="Static 1"/>
<MenuItem Header="Static 2"/>
</Menu>
<Menu Grid.Column="1">
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding HeadText}"/>
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource source}}"/>
</CompositeCollection>
</Menu.ItemsSource>
</Menu>
<Menu Grid.Column="2">
<MenuItem Header="Static 3"/>
</Menu>
</Grid>
</StackPanel>
Maybe there is a nicer solution. Let me know if this is so.

Multiple DataTemplates for one ListBox

Imagine multiple object types in one Listbox. Both have different look and presented information. If I add any of them in ObservableCollection it displays them fine in ListBox.
<DataTemplate DataType="{x:Type local:DogData}" >
<Grid...
</DataTemplate>
<DataTemplate DataType="{x:Type local:CatData}" >
<Grid...
</DataTemplate>
Now I want users to be able to press a button and switch view to see more detailed information and there templates which provide it
<DataTemplate x:Key="TemplateDogDataWithImages" >
<Grid...
</DataTemplate>
<DataTemplate x:Key="TemplateCatDataWithImages" >
<Grid...
</DataTemplate>
but I can assign only one
AnimalsListBox.ItemTemplate = this.Resources["TemplateDogDataWithImages"] as DataTemplate;
I don't want to have one and have a bunch of triggers in it.
I have researched DataTemplateSelectors
http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector
Is there a better way? Is there a way to switch default templates for each data type so I could avoid DataTemplateSelectors?
On click of the button how do I select TemplateDogDataWithImages and TemplateCatDataWithImages as default ones and cliking other button use TemplateDogDataSimple and TemplateCatDataSimple?
I think a selector is easier (less code), but if you really want to use triggers, maybe something like this?
namespace WpfApplication65
{
public abstract class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string property)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public abstract class DataBase : NotifyBase
{
bool m_showDetailed;
public bool ShowDetailed
{
get { return m_showDetailed; }
set
{
m_showDetailed = value;
NotifyPropertyChanged("ShowDetailed");
}
}
}
public class DogData : DataBase { }
public class CatData : DataBase { }
public partial class MainWindow : Window
{
public List<DataBase> Items { get; private set; }
public MainWindow()
{
Items = new List<DataBase>() { new DogData(), new CatData(), new DogData() };
DataContext = this;
InitializeComponent();
}
}
}
<Window x:Class="WpfApplication65.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication65"
Title="MainWindow"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<DataTemplate DataType="{x:Type l:CatData}">
<DataTemplate.Resources>
<DataTemplate x:Key="basic">
<TextBlock Text="This is the basic cat view" />
</DataTemplate>
<DataTemplate x:Key="detailed">
<TextBlock Text="This is the detailed cat view" />
</DataTemplate>
</DataTemplate.Resources>
<Border BorderThickness="1"
BorderBrush="Red"
Margin="2"
Padding="2">
<StackPanel>
<ContentPresenter Name="PART_Presenter"
ContentTemplate="{StaticResource basic}" />
<CheckBox Content="Show Details"
IsChecked="{Binding ShowDetailed}" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ShowDetailed}"
Value="True">
<Setter TargetName="PART_Presenter"
Property="ContentTemplate"
Value="{StaticResource detailed}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate DataType="{x:Type l:DogData}">
<DataTemplate.Resources>
<DataTemplate x:Key="basic">
<TextBlock Text="This is the basic dog view" />
</DataTemplate>
<DataTemplate x:Key="detailed">
<TextBlock Text="This is the detailed dog view" />
</DataTemplate>
</DataTemplate.Resources>
<Border BorderThickness="1"
BorderBrush="Blue"
Margin="2"
Padding="2">
<StackPanel>
<ContentPresenter Name="PART_Presenter"
ContentTemplate="{StaticResource basic}" />
<CheckBox Content="Show Details"
IsChecked="{Binding ShowDetailed}" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ShowDetailed}"
Value="True">
<Setter TargetName="PART_Presenter"
Property="ContentTemplate"
Value="{StaticResource detailed}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding Items}" />
</Window>

Categories

Resources