WPF: ControlTemplate Button override-olása windowból
2015-03-10T14:34:14+01:00
2015-03-13T09:05:27+01:00
2022-08-09T11:50:32+02:00
[OP]Destroy-man
Sziasztok!

Van egy ControlTemplatem, ami window alapértelmezett grafikai megoldását tartalmazza, de annál többröl van szó, ugyanis vezérlők (gombok) is vannak rajta.
Ezt szépen rá is tudom húzni a windowra, viszont ott nem tudom, hogyan tudnám elérni a gomb kattintás eseményét.

Lényegében azt szeretném megoldani, amit WinForms alatt viszonylag egyszerűen meg lehetett csinálni:
Létrehozok egy alap formot (legyen pl. FrmBase), ami az alap kinézetét tárolja a programnak, meg maximum 1-2 alap funkciót.
Ezen kívűl létrehozok egy másik formot, ami maga a program lesz (legyen pl. MainForm), amit az FrmBase-ből származtatok így:

public partial class MainForm : FrmBase
Ezek után simán tudom override-olni a gombok eseményeit. Erre azért van szükség, mert van 2-3 ilyen "sablon", ami ráhuzható szinte az összes formra. Viszont a funkciónalítása mindegyiknek más és más, ezért kell az oz override.

Ezt viszont eddig még nem sikerült megtalálni WPF-ben, hogy hogyan valósítható meg.
A mostani teszt kódom így néz ki:
FrmBaseTemplate.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="WindowTemplate.FrmBaseTemplate"> <ControlTemplate x:Key="BaseTemplate" TargetType="{x:Type Window}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="50"/> <RowDefinition /> <RowDefinition Height="50"/> </Grid.RowDefinitions> <TextBlock Text="Cím"/> <AdornerDecorator Grid.Row="1"> <ContentPresenter /> </AdornerDecorator> <StackPanel Grid.Row="2" Orientation="Horizontal"> <Button Click="Mentes_Click">Mentés</Button> <Button>Mégse</Button> </StackPanel> </Grid> </ControlTemplate> <Style x:Key="FrmBase" TargetType="Window"> <Setter Property="Background" Value="Blue"/> <Setter Property="Template" Value="{StaticResource BaseTemplate}"/> </Style> </ResourceDictionary>
FrmBaseTemplate.cs:

public partial class FrmBaseTemplate : ResourceDictionary { public FrmBaseTemplate() { InitializeComponent(); } public void Mentes_Click(object sender, RoutedEventArgs e) { MessageBox.Show("Eredeti szöveg"); } }
MainWindow.xaml:

<Window x:Class="WpfTemplateDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Template="clr-namespace:WpfTemplateDemo" Title="MainWindow" Height="350" Width="525" Style="{StaticResource FrmBase}"> <Grid/> </Window>
A feladat, hogy a MainWindow.cs-ből tudjam override-olni az FrmBaseTemplate-ben lévő Mentes_Click eseményt.

A segítséget, tippeket előre is köszönöm.
Mutasd a teljes hozzászólást!
az a nagy baj, hogy az általam adott linket nem értetted meg, csak elolvastad

1. Ahogy már írták ne a window-t csapd felül
2. csinálj egy customcontrolt amit a ContentControlból származtass
3. ebben annyi dependencyproperty ICommand kell, ahány gombod van. Mindegyik default értéke egy függvény a control-ban
4. készítsd el hozzá a stílust (ahányat csak akarsz, de 1 minimum kell).
a gombokat bindold hozzá a dependency propertykhez

a window-ban kell namesapce, ami a kontrolra mutat
kell egy resorce hivatkozás a stílus xaml-re

az xaml-ben "példányosítsd" a kontrolt, bindold hozzá a saját függvényeidet (itt csapod fejbe az eredeti függvényhívást)
a contentjébe meg azt raksz, amit akarsz
Mutasd a teljes hozzászólást!

  • Helló!

    Hát találtam egy megoldást, de mit is mondjak... elég kókány. :D Gondolom máshogy kellene ezt a dolgot megfogni, de nem vagyok otthon wpf-ben.

    MainWindow.xaml

    <Window ...... Loaded="Window_Loaded" >
    MainWindows.cs

    private void Window_Loaded(object sender, RoutedEventArgs e) { var myButton = this.GetTemplateChild("btnSave") as Button; if (myButton != null) { myButton.Tag = "-"; myButton.Click += myButton_Click; } } void myButton_Click(object sender, RoutedEventArgs e) { MessageBox.Show("teszt"); }
    FrmBaseTemplate.xaml

    ...<Button x:Name="btnSave" Click="Mentes_Click">Mentés</Button>...
    FrmBaseTemplate.cs

    public void Mentes_Click(object sender, RoutedEventArgs e) { if (((Button)sender).Tag == null) MessageBox.Show("Eredeti szöveg"); }
    Csatoltam is a projektet egyébként.
    Mutasd a teljes hozzászólást!
    Csatolt állomány
  • A legelső, amit meg kell értened, hogy a WPF az nem WinForm.
    Azt a logikát gyorsan el kell felejtened és helyette megtanulni az adatkötést

    Ezt mind csináld végig

    Ha mind elolvasod, akkor
    Kell 1 dependencyproperty(ICommand)
    és egy bind
    Mutasd a teljes hozzászólást!
  • Köszi a leírást, sok hasznos infó volt benne.

    Viszont amit szeretnék megvalósítani, azt még nem látom át. Vagyis a más DLL-ben lévő gomb click eseményét hogyan tudnám fellül bírálni. Igazándiból úgy ahogy van az alap esemény szeretném kivágni, és helyette egy teljesen más eseménynek kéne lefutni. Az nem egészen járható út, hogy ViewModelbe meghatározom, hogy mit csináljon, mert ahányszor fel lesz használva a kód, annyi féle eseménynek kéne lefutnia. Szóval kódnak jó lenne a window közelében lennie. Egyáltalán ilyent tud a WPF?
    Mutasd a teljes hozzászólást!
  • Köszi, de ezt megoldást csak végszükséglet esetén szeretném bevetni. :)
    Mutasd a teljes hozzászólást!
  • Amit linkeltem, az csak egy cikk a sokk közül
    mindet olvasd végig
    Ezek nélkül nem lehet elmondani, hogy mit csinálj
    A legvégén ezt az OnClick eseményt úgy ahogy van dobod majd a francba, de addig el kell jutnod, és addig nem is vagyok hajlandó segíteni, amíg az alapokat meg nem érted
    Mutasd a teljes hozzászólást!
  • Végig olvastam és csináltam az összeset.

    Az adatkötésekkel, konverterekkel már tisztában vagyok. Egyedül ez az esemény meghívás nem megy.
    Mutasd a teljes hozzászólást!
  • Részemről (mivel a wpf window nem leszármaztatható) a problémát Page-ekkel oldottam meg.
    A window nem módosul, vagyis a 'base' window lett a 'working' és a konkrét változásokat más-más page betöltéssel oldom meg, azoknak meg ugye saját event kezelő forrása van és lehet saját vagy közös viewmodelje is.
    De a page helyett használhatsz UserControl-t is, az is nagyon jó bír lenni.
    De még futásidőben betöltött xaml-el is megbolondítom, vagyis igencsak sokféle a wpf által kínált lehetőség, lényegesen több, mint a winform-nál.
    Vagyis nézd át a lehetőségeket, lehet számtalan utat találni a megoldáshoz.
    Mutasd a teljes hozzászólást!
  • Találtam rá egy egyszerű megoldást.
    Hogy mennyire szép az jó kérdés. :)

    FrmBaseTemplate.xaml.cs

    public partial class FrmBaseTemplate : ResourceDictionary { public static event EventHandler Mentes; public FrmBaseTemplate() { InitializeComponent(); } public virtual void Mentes_Click(object sender, RoutedEventArgs e) { Mentes.Invoke(this, e); } }
    MainWindow.xaml.cs

    public MainWindow() { InitializeComponent(); FrmBaseTemplate.Mentes += FrmBaseTemplate_Mentes; } void FrmBaseTemplate_Mentes(object sender, EventArgs e) { MessageBox.Show("Teszt"); }
    Mást nem változtattam a kódban, tehát az XAML-ek maradtak.
    Mutasd a teljes hozzászólást!
  • az a nagy baj, hogy az általam adott linket nem értetted meg, csak elolvastad

    1. Ahogy már írták ne a window-t csapd felül
    2. csinálj egy customcontrolt amit a ContentControlból származtass
    3. ebben annyi dependencyproperty ICommand kell, ahány gombod van. Mindegyik default értéke egy függvény a control-ban
    4. készítsd el hozzá a stílust (ahányat csak akarsz, de 1 minimum kell).
    a gombokat bindold hozzá a dependency propertykhez

    a window-ban kell namesapce, ami a kontrolra mutat
    kell egy resorce hivatkozás a stílus xaml-re

    az xaml-ben "példányosítsd" a kontrolt, bindold hozzá a saját függvényeidet (itt csapod fejbe az eredeti függvényhívást)
    a contentjébe meg azt raksz, amit akarsz
    Mutasd a teljes hozzászólást!
  • Megcsináltam ahogy írtad, de a stíluslapnál az adatkötés valószinű - sőt, biztos -, hogy rosszul csinálom. Ezt a hibát dobja a VS az outputra (program hiba nincs):

    System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=custControl'. BindingExpression:Path=MentesCommand; DataItem=null; target element is 'Button' (Name='Mentes'); target property is 'Command' (type 'ICommand')

    System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=custControl'. BindingExpression:Path=MegseCommand; DataItem=null; target element is 'Button' (Name='Megse'); target property is 'Command' (type 'ICommand')

    A forrás kódok:
    CustomControl.cs

    public partial class CustomControl : ContentControl { public CustomControl() : base() { } public static readonly DependencyProperty MentesCommandProperty = DependencyProperty.Register( "MentesCommand", typeof(ICommand), typeof(CustomControl)); public ICommand MentesCommand { get { return (ICommand)GetValue(CustomControl.MentesCommandProperty); } set { SetValue(CustomControl.MentesCommandProperty, value); } } public static readonly DependencyProperty MegseCommandProperty = DependencyProperty.Register( "MegseCommand", typeof(ICommand), typeof(CustomControl)); public ICommand MegseCommand { get { return (ICommand)GetValue(CustomControl.MegseCommandProperty); } set { SetValue(CustomControl.MegseCommandProperty, value); } } }

    FrmBaseTemplate.xaml

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:control="clr-namespace:UjWpfTemplate" x:Class="UjWpfTemplate.FrmBaseTemplate"> <Style x:Key="FrameBorder" TargetType="Border"> <Setter Property="CornerRadius" Value="10, 10, 0, 0" /> <Setter Property="Background" Value="#Ababab" /> <Setter Property="BorderBrush" Value="DimGray" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="HorizontalAlignment" Value="Stretch" /> <Setter Property="VerticalAlignment" Value="Stretch" /> <Setter Property="Opacity" Value="80" /> </Style> <ControlTemplate x:Key="BaseTemplate" TargetType="{x:Type Window}"> <Border Name="WindowFrame" Style="{StaticResource FrameBorder}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition /> <RowDefinition Height="50"/> </Grid.RowDefinitions> <Border MouseLeftButtonDown="titleBar_MouseLeftButtonDown" Padding="15" CornerRadius="10, 10, 0, 0"> <Border.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FFABABAB"/> <GradientStop Color="#FF202020" Offset="1"/> </LinearGradientBrush> </Border.Background> </Border> <TextBlock Foreground="White" Text="{TemplateBinding Title}" MouseLeftButtonDown="titleBar_MouseLeftButtonDown" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Normal" /> <AdornerDecorator Grid.Row="1"> <ContentPresenter /> </AdornerDecorator> <StackPanel Grid.Row="2" Orientation="Horizontal"> <StackPanel.Resources> <control:CustomControl x:Key="custControl"/> </StackPanel.Resources> <Button Name="Mentes" Command="{Binding MentesCommand, ElementName=custControl}">Mentés</Button> <Button Name="Megse" Command="{Binding MegseCommand, ElementName=custControl}">Mégse</Button> </StackPanel> </Grid> </Border> </ControlTemplate> <Style x:Key="FrmBase" TargetType="Window"> <Setter Property="Background" Value="Transparent"/> <Setter Property="WindowStyle" Value="None"/> <Setter Property="AllowsTransparency" Value="True"/> <Setter Property="Opacity" Value="95" /> <Setter Property="Template" Value="{StaticResource BaseTemplate}"/> </Style> </ResourceDictionary>

    MainWindow.xaml

    <Window x:Class="UjWpfTemplate.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:base="clr-namespace:UjWpfTemplate" Title="MainWindow" Height="350" Width="525" Style="{StaticResource FrmBase}" WindowStartupLocation="CenterScreen"> <Window.Resources> <base:CustomControl x:Key="Control" MentesCommand="{Binding Mentes}" MegseCommand="{Binding Megse}"/> </Window.Resources> <Grid> </Grid> </Window>
    MainWindow.xaml.cs

    public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } public void Mentes(object sender, RoutedEventArgs e) { MessageBox.Show("Mentés"); } public void Megse(object sender, RoutedEventArgs e) { MessageBox.Show("Mégse"); } }
    A dependency property-k inicializálása megtörténik, de utána már a gombra való kattintáskor nem történik semmi.
    Mutasd a teljes hozzászólást!
  • using System; using System.ComponentModel; using System.Windows.Input; namespace CommonButtons { public class SimpleCommand : INotifyPropertyChanged, ICommand { public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } bool _isEnabled = true; public bool IsEnabled { get { return _isEnabled; } set { _isEnabled = value; OnPropertyChanged("IsEnabled"); } } private readonly Action _executeMethod; private readonly Action<object> _parametrizedMethod; public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public SimpleCommand(Action action) { _executeMethod = action; } public SimpleCommand(Action<object> action) { _parametrizedMethod = action; } public bool CanExecute(object parameter) { return IsEnabled; } public void Execute(object parameter) { if (_executeMethod != null) _executeMethod.Invoke(); if (_parametrizedMethod != null) _parametrizedMethod.Invoke(parameter); } } }
    Mutasd a teljes hozzászólást!
  • using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace CommonButtons { public class PageContainer : ContentControl { #region Dependency properties public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register("ButtonCommand", typeof(ICommand), typeof(PageContainer), new PropertyMetadata(new SimpleCommand(ExecuteButtonClick))); public ICommand ButtonCommand { get { return (ICommand)GetValue(ButtonCommandProperty); } set { SetValue(ButtonCommandProperty, value); } } #endregion // ******************************************************************** #region Constructors static PageContainer() { DefaultStyleKeyProperty.OverrideMetadata(typeof(PageContainer), new FrameworkPropertyMetadata(typeof(PageContainer))); } #endregion // ******************************************************************** private static void ExecuteButtonClick() { System.Diagnostics.Debug.WriteLine("This is executed in control"); } } }
    Mutasd a teljes hozzászólást!
  • <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Controls="clr-namespace:CommonButtons"> <Style TargetType="{x:Type Controls:PageContainer}"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Margin" Value="0"/> <Setter Property="Padding" Value="0"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Controls:PageContainer}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <ContentPresenter Grid.Column="0"/> <Button Grid.Column="1" Width="50" Height="30" Content="Press" Command="{Binding Path=ButtonCommand, RelativeSource={RelativeSource TemplatedParent}}"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
    Mutasd a teljes hozzászólást!
  • <Window x:Class="CommonButtons.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" xmlns:Controls="clr-namespace:CommonButtons"> <Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/CommonButtons;component/PageContainer.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <Grid> <Controls:PageContainer ButtonCommand="{Binding OverrideCommand}"> <TextBlock Text="This is the content of the page"/> </Controls:PageContainer> </Grid> </Window>
    Mutasd a teljes hozzászólást!
  • using System.ComponentModel; using System.Windows.Input; namespace CommonButtons { public partial class MainWindow : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } ICommand _overrideCommand; public ICommand OverrideCommand { get { return _overrideCommand; } set { _overrideCommand = value; OnPropertyChanged("OverrideCommand"); } } public MainWindow() { InitializeComponent(); OverrideCommand = new SimpleCommand(ExecuteOverrideCommand); DataContext = this; } private void ExecuteOverrideCommand() { System.Diagnostics.Debug.WriteLine("Execute override"); } } }
    Mutasd a teljes hozzászólást!
  • Köszi a részletes kódot.
    Így már sokkal érthetőbb számomra. :)
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd