|
(Again, another complete newbie).
I'm trying to bind an ItemSource to a ListView.
From what I understand, the data should be accessible via a Property.
The main Model is "MyModel" it contains a list of items. (eventually will contain lot more data).
I'm not sure what incantation I'm missing.
Thanks.
public class Item
{
public string Name { get; set; }
public Item(string name)
{
Name=name;
}
}
public class ItemsList
{
public List<Item> Items
{
get; private set;
}
public ItemsList()
{
Items = new List<Item>
{
new Item("a"),
new Item("b"),
new Item("c")
};
}
}
public class MyModel
{
private readonly ItemsList _list;
public List<Item> Items => _list.Items;
public MyModel()
{
_list = new ItemsList();
}
}
App.xaml.ca :
protected override void OnStartup(StartupEventArgs e)
{
MainWindow = new MainWindow()
{
DataContext = new MyModel()
};
MainWindow.Show();
base.OnStartup(e);
}
MainWindow :
<Grid>
<!-- here ... pass the datacontext to my component. -->
<components:MyListView DataContext="MyModel"/>
</Grid>
MyListView.xaml component. :
<Grid>
<!-- here ... what binding source should I put in ? -->
<ListView Margin="10" ItemsSource="{Binding Items}"/>
</Grid>
CI/CD = Continuous Impediment/Continuous Despair
modified 8-Dec-22 10:23am.
|
|
|
|
|
Maximilien wrote:
<components:MyListView DataContext="MyModel"/> That line's wrong for a start. The DataContext has already been set for the window, and will be inherited by the MyListView control.
Maximilien wrote:
<ListView Margin="10" ItemsSource="{Binding Items}"/> That looks correct. Are you getting any binding errors in the output window?
XAML data binding diagnostics - Visual Studio (Windows) | Microsoft Learn[^]
NB: Your view-models should really implement INotifyPropertyChanged[^], and the collection should be an ObservableCollection<T>[^], so that the binding system can pick up any changes. But at the moment you don't seem to be making any changes, so that's unlikely to be the issue.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
|
I'm writing a WPF (C#) app for my MP3 collection with more then 350,000 files.
I'm reading path and filenames directly from the HD and store the infos in a SQLite Database.
Because of reorganizationes I have to do that in periodic intervals.
But the current code needs 17 hours (!!!) for that. I'm sure there must be a faster way.
Does anyone have an idea?
private void ButtonImport_Click(object sender, RoutedEventArgs e)
{
string initdir = @"H:\mp3\";
Cursor cu = this.Cursor;
this.Cursor = Cursors.Wait;
string errtxt = "";
ToolStripStatusLabel1.Text = "deleting ..."
try
{
SQLiteCommand dbCom = new SQLiteCommand("DELETE FROM Dateien", dbConn);
dbCom.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "delete error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
sPfad = initdir;
DateTime t1 = DateTime.Now;
int z = 0;
loopDir(sPfad);
DateTime t2 = DateTime.Now;
txt = "";
txt += z.ToString() + " files saved in ";
txt += ((t2 - t1).TotalSeconds / 60).ToString("0.0") + " min.";
txt += errtxt != "" ? Environment.NewLine + Environment.NewLine + errtxt : "";
this.Cursor = cu;
MessageBox.Show(txt, "Import", MessageBoxButton.OK, MessageBoxImage.Information);
}
private void loopDir(string strPath)
{
string dn = "";
DirectoryInfo ofs = new DirectoryInfo(strPath);
bool f = false;
int n = 0;
try
{
n = ofs.GetDirectories().Length;
}
catch (Exception ex)
{
f = true;
errtxt += ex.Message + Environment.NewLine;
}
if (f == false)
{
foreach (DirectoryInfo d in ofs.GetDirectories())
{
dn = strPath + "\\" + d.Name;
dn = dn.Replace("\\\\", "\\");
loopDir(dn);
}
bool ok = false;
foreach (FileInfo fl in ofs.GetFiles())
{
ok = false;
for (int i = 0; i < fext.Length; i++)
{
if (fl.Name.ToLower().EndsWith(fext[i]))
ok = true;
}
if (ok == true)
{
string sqls = "";
sqls += "INSERT INTO Dateien (Pfad, Datei) VALUES (";
sqls += "'" + sqlready(strPath).ToString().Substring(2) + "\\', ";
sqls += "'" + sqlready(fl.Name) + "')";
try
{
SQLiteCommand dbCom = new SQLiteCommand(sqls, dbConn);
dbCom.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "database save error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
z++;
}
}
}
}
Thanks
|
|
|
|
|
I found an answer myself:
Reading all 350,000 files from the HD takes 6.2 minutes (!!!).
So the bottleneck is SQLite and I think I have to use SQL transactions to speed up my app.
|
|
|
|
|
Transactions do not speed up database operations. Transactions encapsulate multiple operations so they execute as a single set. If any operation fails, all of the operations in the transaction are rolled back.
What you should have been doing is using a parameterized INSERT query, and NOT that string concatenation crap.
|
|
|
|
|
Well, that code is ... interesting. There's a lot of needless work going on there.
Try something like this instead:
private void loopDir(DirectoryInfo folder, DataTable table, List<string> errors)
{
try
{
foreach (DirectoryInfo subFolder in folder.EnumerateDirectories())
{
loopDir(subFolder, table, errors);
}
string pfad = folder.FullName.Substring(2);
foreach (FileInfo file in folder.EnumerateFiles())
{
string extension = Path.GetExtension(file.Name);
if (!fext.Contains(extension)) continue;
table.Rows.Add(pfad, file.Name);
}
}
catch (Exception ex)
{
errors.Add(ex.Message);
}
}
private int ImportFiles(string initialFolder, List<string> errors)
{
DirectoryInfo folder = new DirectoryInfo(initialFolder);
DataTable table = new DataTable();
table.Columns.Add("Pfad", typeof(string));
table.Columns.Add("Datei", typeof(string));
loopDir(folder, table, errors);
if (table.Rows.Count == 0)
{
MessageBox.Show("No files found to import.", "Import", MessageBoxButton.OK, MessageBoxImage.Error);
return 0;
}
using (SQLiteTransaction dbTrans = dbConn.BeginTransaction())
{
using (SQLiteCommand dbCom = new SQLiteCommand("DELETE FROM Dateien", dbConn, dbTrans))
{
dbCom.ExecuteNonQuery();
}
using (SQLiteCommand dbCom = new SQLiteCommand("INSERT INTO Dateien (Pfad, Datei) VALUES ($Pfad, $Datei)", dbConn, dbTrans))
{
SQLiteParameter pPfad = dbCom.CreateParameter();
pPfad.ParameterName = "$Pfad";
dbCom.Parameters.Add(pPfad);
SQLiteParameter pDatei = dbCom.CreateParameter();
pDatei.ParameterName = "$Datei";
dbCom.Parameters.Add(pDatei);
foreach (DataRow row in table.Rows)
{
pPfad.Value = row["Pfad"];
pDatei.Value = row["Datei"];
dbCom.ExecuteNonQuery();
}
}
transaction.Commit();
}
return table.Rows.Count;
}
private void ButtonImport_Click(object sender, RoutedEventArgs e)
{
Cursor cu = this.Cursor;
this.Cursor = Cursors.Wait;
try
{
const string initdir = @"H:\mp3\";
List<string> errors = new List<string>();
Stopwatch sw = Stopwatch.StartNew();
int recordCount = ImportFiles(initdir);
sw.Stop();
string message = $"{recordCount} files saved in {sw.Elapsed.TotalMinutes:0.0} minutes.";
if (errors.Count != 0) message += Environment.NewLine + Environment.NewLine + string.Join(Environment.NewLine, errors);
MessageBox.Show(message, "Import", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Import Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
this.Cursor = cu;
}
}
NB: SQLite doesn't have a "bulk insert" option, so you need to use a transaction and a single SQLiteCommand to perform the insert:
Bulk insert - Microsoft.Data.Sqlite | Microsoft Learn[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
modified 7-Dec-22 4:38am.
|
|
|
|
|
Thanks Richard, thats really great.
A little correction to the following line:
using (SQLiteCommand dbCom = new SQLiteCommand("INSERT INTO Dateien (Pfad, Datei) VALUES ($Pfad, $Datei)"))
to
using (SQLiteCommand dbCom = new SQLiteCommand("INSERT INTO Dateien (Pfad, Datei) VALUES ($Pfad, $Datei)", dbConn, dbTrans))
And now it works perfectly.
And wow, it's done in 12 seconds !!! Very impressive !!!
THANKS!
|
|
|
|
|
I have created a Hyperlink control. What I want is to have a default style in my control itself, but allow consumers to restyle it later, like when It's dropped into a window.
So here's my control's XAML:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Marois.Framework.Core.WPF.Controls"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors">
<SolidColorBrush x:Key="hyperlinkMouseOverBrush" Color="Green"/>
<Style TargetType="{x:Type local:MaroisHyperlink}">
<Setter Property="FontSize" Value="60" />
<Setter Property="Hyperlink.TextDecorations" Value="None" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock>
<Hyperlink>
<TextBlock Text="{Binding LinkText, RelativeSource={RelativeSource TemplatedParent}}"
FontSize="{TemplateBinding FontSize}"/>
</Hyperlink>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Hyperlink.Foreground" Value="{StaticResource hyperlinkMouseOverBrush}" />
<Setter Property="Hyperlink.TextDecorations" Value="Underline" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Hyperlink.Foreground" Value="Gray" />
<Setter Property="Hyperlink.TextDecorations" Value="None" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Code behind:
using System.Windows;
using System.Windows.Media;
namespace Marois.Framework.Core.WPF.Controls
{
public class MaroisHyperlink : ControlBase
{
#region DP's
#region DP HoverBrush
public static readonly DependencyProperty HoverBrushProperty =
DependencyProperty.Register("HoverBrush",
typeof(SolidColorBrush),
typeof(MaroisHyperlink),
new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public SolidColorBrush HoverBrush
{
get { return (SolidColorBrush)GetValue(HoverBrushProperty); }
set { SetValue(HoverBrushProperty, value); }
}
#endregion
#region DP LinkText
public static readonly DependencyProperty LinkTextProperty =
DependencyProperty.Register("LinkText",
typeof(string),
typeof(MaroisHyperlink),
new PropertyMetadata("MaroisHyperlink"));
public string LinkText
{
get { return (string)GetValue(LinkTextProperty); }
set { SetValue(LinkTextProperty, value); }
}
#endregion
#endregion
#region CTOR
static MaroisHyperlink()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MaroisHyperlink),
new FrameworkPropertyMetadata(typeof(MaroisHyperlink)));
}
#endregion
}
}
Some of this works:
- LinkText DP works. I can set it's default value and that shows up when I put the control on the window. Changing it in the window works also
- The FontSize setter at the top works.
- The Hyperlink.TextDecorations does not.
- None of the triggers at the bottom work.
In the Window
<Window x:Class="Marois.Framework.Core.WPF.Controls.Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:marois="clr-namespace:Marois.Framework.Core.WPF.Controls;assembly=Marois.Framework.Core.WPF.Controls"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="MainWindow"
Height="450"
Width="800">
<Window.Resources>
<Style TargetType="{x:Type marois:MaroisHyperlink}">
<Setter Property="Foreground" Value="DarkTurquoise"/>
<Setter Property="HoverBrush" Value="Brown"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Resources>
<Grid>
<marois:MaroisHyperlink Grid.Row="0"
Grid.Column="0"
LinkText="Click me!"
Margin="5">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LinkClicked">
<i:InvokeCommandAction Command="{Binding MaroisLinkClickedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</marois:MaroisHyperlink>
</Grid>
</Window>
In the style
- The FontSize works
- The HoverBrush does not work
- The Foreground does not work
So basically I can style the FontSize, but nothing else. I'm not really sure how to make this all work. I'd like it to be like any other 3rd party control - Have default values that you can restyle.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Kevin Marois wrote: The Hyperlink.TextDecorations does not.
Firstly, it's Inline.TextDecorations , not Hyperlink.TextDecorations .
Inline.TextDecorations Property (System.Windows.Documents) | Microsoft Learn[^]
Setting that property on a parent element doesn't affect the Hyperlink , since it overrides the property in its default template. You either need to set the property explicitly on the Hyperlink :
<TextBlock>
<Hyperlink TextDecorations="None"> or via a style:
<TextBlock>
<Style TargetType="Hyperlink">
<Setter Property="TextDecorations" Value="None" />
</Style>
<Hyperlink> If you want to be able to override it when you use your control, you'll need a property to control it:
public class MaroisHyperlink : ControlBase
{
public static readonly DependencyProperty TextDecorationsProperty = Inline.TextDecorationsProperty.AddOwner(typeof(MaroisHyperlink));
public TextDecorationCollection TextDecorations
{
get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }
set { SetValue(TextDecorationsProperty, value); }
} and then bind the property in your template:
<Hyperlink TextDecorations="{TemplateBinding TextDecorations}">
Kevin Marois wrote: None of the triggers at the bottom work.
A similar problem. The triggers should set the properties of your control, and the relevant properties of the Hyperlink should be bound to the properties on your control.
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Foreground" Value="{TemplateBinding HoverBrush}" />
<Setter Property="TextDecorations" Value="Underline" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="Gray" />
<Setter Property="TextDecorations" Value="None" />
</Trigger>
</Style.Triggers>
<Hyperlink TextDecorations="{TemplateBinding TextDecorations}" Foreground="{TemplateBinding Foreground}>
Kevin Marois wrote: The HoverBrush does not work
The HoverBrush property isn't used anywhere in your template. Changing the triggers as per the previous paragraph should resolve that, as well as the foreground brush styling.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks. I learned some new things here.
One question -
That DP syntax you used is unfamiliar to me. How do you set the default value for the TextDecoration's DP?
I've always used this syntax for a DP, but for some reason it won't compile here:
public static readonly DependencyProperty TextDecorationsProperty =
DependencyProperty.Register("TextDecorations",
typeof(TextDecorations),
typeof(MaroisHyperlink),
new PropertyMetadata(TextDecorations.Underline));
public TextDecorations TextDecorations
{
get { return (TextDecorations)GetValue(TextDecorationsProperty);}
set { SetValue(TextDecorationsProperty, value); }
}
I get 3 compilation errors:
Cannot convert to static type 'TextDecorations'
'TextDecorations': static types cannot be used as return types
'TextDecorations': static types cannot be used as paramters
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
|
I stumbled across this also. I needed put the triggers INSIDE the template:
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="hyperLink" Property="TextDecorations" Value="{Binding TextDecorations, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter TargetName="hyperLink" Property="Foreground" Value="{Binding HoverBrush, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I beleive I finally have it working! This was mainly about me learning how to create Custome Controls. I appreciate all your help. I learned a lot.
Here's the finiished product!
Generic.xaml
<Style TargetType="{x:Type local:MaroisHyperlink}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock>
<Hyperlink x:Name="hyperLink"
Foreground="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}"
TextDecorations="{Binding TextDecorations, RelativeSource={RelativeSource TemplatedParent}}">
<TextBlock Text="{Binding LinkText,
RelativeSource={RelativeSource TemplatedParent}}"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=Control}, Path=LinkClickedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Hyperlink>
</TextBlock>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="hyperLink" Property="TextDecorations" Value="{Binding TextDecorations, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter TargetName="hyperLink" Property="Foreground" Value="{Binding HoverBrush, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Code Behind
public class MaroisHyperlink : ControlBase
{
#region Routed Events
#region LinkClickedEvent
public static readonly RoutedEvent LinkClickedEvent =
EventManager.RegisterRoutedEvent("LinkClicked",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MaroisHyperlink));
public event RoutedEventHandler LinkClicked
{
add { AddHandler(LinkClickedEvent, value); }
remove { RemoveHandler(LinkClickedEvent, value); }
}
private void RaiseLinkClickedEvent()
{
RoutedEventArgs args = new RoutedEventArgs(LinkClickedEvent);
RaiseEvent(args);
}
#endregion
#endregion
#region DP's
#region DP TextDecorations
public static readonly DependencyProperty TextDecorationsProperty =
Inline.TextDecorationsProperty.AddOwner(typeof(MaroisHyperlink));
public TextDecorationCollection TextDecorations
{
get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }
set { SetValue(TextDecorationsProperty, value); }
}
#endregion
#region DP HoverBrush
public static readonly DependencyProperty HoverBrushProperty =
DependencyProperty.Register("HoverBrush",
typeof(SolidColorBrush),
typeof(MaroisHyperlink),
new PropertyMetadata(new SolidColorBrush(Colors.Green)));
public SolidColorBrush HoverBrush
{
get { return (SolidColorBrush)GetValue(HoverBrushProperty); }
set { SetValue(HoverBrushProperty, value); }
}
#endregion
#region DP LinkText
public static readonly DependencyProperty LinkTextProperty =
DependencyProperty.Register("LinkText",
typeof(string),
typeof(MaroisHyperlink),
new PropertyMetadata("MaroisHyperlink"));
public string LinkText
{
get { return (string)GetValue(LinkTextProperty); }
set { SetValue(LinkTextProperty, value); }
}
#endregion
#endregion
#region Commands
private ICommand? _LinkClickedCommand;
public ICommand LinkClickedCommand
{
get
{
if (_LinkClickedCommand == null)
_LinkClickedCommand = new RelayCommand(LinkClickedExecuted);
return _LinkClickedCommand;
}
}
#endregion
#region CTOR
static MaroisHyperlink()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MaroisHyperlink),
new FrameworkPropertyMetadata(typeof(MaroisHyperlink)));
}
#endregion
#region Private Methods
private void LinkClickedExecuted()
{
RaiseLinkClickedEvent();
}
#endregion
}
Usage, with overriding style
<Window x:Class="Marois.Framework.Core.WPF.Controls.Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:marois="clr-namespace:Marois.Framework.Core.WPF.Controls;assembly=Marois.Framework.Core.WPF.Controls"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="Marois Controls Demo"
Height="450"
Width="800">
<Window.Resources>
<Style TargetType="{x:Type marois:MaroisHyperlink}">
<Setter Property="HoverBrush" Value="OliveDrab"/>
<Setter Property="Foreground" Value="Cyan"/>
<Setter Property="FontSize" Value="30"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Foreground" Value="BlanchedAlmond" />
<Setter Property="TextDecorations" Value="Underline" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="LightGray" />
<Setter Property="TextDecorations" Value="None" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<CheckBox x:Name="checkBox"
Content="Enabled"
IsChecked="True"
Margin="5"/>
<marois:MaroisHyperlink x:Name="link"
Margin="5"
IsEnabled="{Binding ElementName=checkBox, Path=IsChecked}"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LinkClicked">
<i:InvokeCommandAction Command="{Binding MaroisLinkClickedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</marois:MaroisHyperlink>
</StackPanel>
</Grid>
</Window>
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Following this SO article, I'm trying to create a relay command that takes parameters:
<ctrls:MaroisHyperlink LinkText="Forgot Password?"
Foreground="SteelBlue"
Margin="30,0,0,0"
Height="20">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LinkClicked">
<i:InvokeCommandAction Command="{Binding ForgotPasswordClickedCommand}"
CommandParameter="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ctrls:MaroisHyperlink>
Here's my RelayCommand<t> class:
namespace Marois.Framework.Core.Utilities
{
public class RelayCommand<T> : ICommand
{
private readonly Action<T> execute;
private readonly Func<T, bool> canExecute;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
{
ArgumentNullException.ThrowIfNull(execute);
this.execute = execute;
this.canExecute = canExecute;
this.RaiseCanExecuteChangedAction = RaiseCanExecuteChanged;
SimpleCommandManager.AddRaiseCanExecuteChangedAction(ref RaiseCanExecuteChangedAction);
}
~RelayCommand()
{
RemoveCommand();
}
public void RemoveCommand()
{
SimpleCommandManager.RemoveRaiseCanExecuteChangedAction(RaiseCanExecuteChangedAction);
}
bool ICommand.CanExecute(object? parameter)
{
return canExecute((T)parameter);
}
public void Execute(object? parameter)
{
if (CanExecute(parameter))
{
execute((T)parameter);
}
}
public bool CanExecute(object? parameter)
{
return canExecute == null ? true : canExecute((T)parameter);
}
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, new EventArgs());
}
}
private readonly Action RaiseCanExecuteChangedAction;
public event EventHandler CanExecuteChanged;
}
public static class SimpleCommandManager
{
private static List<Action> _raiseCanExecuteChangedActions = new List<Action>();
public static void AddRaiseCanExecuteChangedAction(ref Action raiseCanExecuteChangedAction)
{
_raiseCanExecuteChangedActions.Add(raiseCanExecuteChangedAction);
}
public static void RemoveRaiseCanExecuteChangedAction(Action raiseCanExecuteChangedAction)
{
_raiseCanExecuteChangedActions.Remove(raiseCanExecuteChangedAction);
}
public static void AssignOnPropertyChanged(ref PropertyChangedEventHandler propertyEventHandler)
{
propertyEventHandler += OnPropertyChanged;
}
private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "CanExecute")
{
RefreshCommandStates();
}
}
public static void RefreshCommandStates()
{
for (var i = 0; i < _raiseCanExecuteChangedActions.Count; i++)
{
var raiseCanExecuteChangedAction = _raiseCanExecuteChangedActions[i];
if (raiseCanExecuteChangedAction != null)
{
raiseCanExecuteChangedAction.Invoke();
}
}
}
}
}
and here's the VM
private ICommand? _ForgotPasswordClickedCommand;
public ICommand ForgotPasswordClickedCommand
{
get
{
if (_ForgotPasswordClickedCommand == null)
_ForgotPasswordClickedCommand = new RelayCommand<bool>(p => ForgotPasswordLinkClickedExecuted(p), a => ForgotPasswordCanExecute(a));
return _ForgotPasswordClickedCommand;
}
}
private bool ForgotPasswordCanExecute(bool args)
{
return true;
}
private void ForgotPasswordLinkClickedExecuted(bool args)
{
}
When I set the parameter type to Object then it works fine. But if I set it to Bool, as above, then I get the following exception:
'Unable to cast object of type 'System.String' to type 'System.Boolean'.'
I have an implementation of this a WPF .Net Framework and it works fine. But in the .Net Core app I get the error.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I have a WPF app with a Login window. I'd like to add a "Forgor Password?" link in it. The question is, how would I implement it?
The app has an Employees page. I could put something there that could be checked when the link is clicked.
Another option would be to send an email to the user, but then what?
Any ideas?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
|
I'm trying to create what I thought would be a simple custom control to implement a hyperlink.
This all works in WPF .Net Framework, but in .Net Core I have 2 problems. First, here's the code:
XAML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MaroisHyperlink"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors">
<pre>
<Style TargetType="{x:Type local:MaroisHyperlink}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock>
<Hyperlink>
<TextBlock Text="{Binding LinkText}"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding LinkClickedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Hyperlink>
</TextBlock>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Code Behind
using Marois.Framework.Core.Utilities;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace MaroisHyperlink
{
public class MaroisHyperlink : Control
{
#region Commands
private ICommand? _LinkClickedCommand;
public ICommand LinkClickedCommand
{
get
{
if (_LinkClickedCommand == null)
_LinkClickedCommand = new RelayCommand(LinkClickedExecuted, LinkClickedCanExecute);
return _LinkClickedCommand;
}
}
#endregion
#region DP LinkText
public static readonly DependencyProperty LinkTextProperty =
DependencyProperty.Register("LinkText",
typeof(string),
typeof(MaroisHyperlink),
new PropertyMetadata("", new PropertyChangedCallback(OnLinkTextChanged)));
public string LinkText
{
get { return (string)GetValue(LinkTextProperty); }
set { SetValue(LinkTextProperty, value); }
}
private static void OnLinkTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
#endregion
#region LinkClickedEvent
public static readonly RoutedEvent LinkClickedEvent =
EventManager.RegisterRoutedEvent("LinkClicked",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MaroisHyperlink));
public event RoutedEventHandler LinkClicked
{
add { AddHandler(LinkClickedEvent, value); }
remove { RemoveHandler(LinkClickedEvent, value); }
}
private void RaiseLinkClickedEvent()
{
RaiseEvent(new RoutedEventArgs(LinkClickedEvent));
}
#endregion
#region CTOR
static MaroisHyperlink()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MaroisHyperlink),
new FrameworkPropertyMetadata(typeof(MaroisHyperlink)));
}
#endregion
#region Private Methods
private bool LinkClickedCanExecute()
{
return IsEnabled;
}
private void LinkClickedExecuted()
{
RaiseLinkClickedEvent();
}
#endregion
}
}
Window
<Window x:Class="HyperlinkDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:link="clr-namespace:MaroisHyperlink;assembly=MaroisHyperlink"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d"
Title="MainWindow"
Height="300"
Width="500">
<pre>
<Grid>
<link:MaroisHyperlink LinkText="Click This Link!"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"/>
</Grid>
Problems
1. The LinkText is not appearing. I get no binding errors, but when I run it, there's nothing showing. If I set the inner TextBox's text to something, then it shows up.
<TextBlock>
<pre>
<Hyperlink>
<TextBlock Text="THIS SHOWS UP"/>
</Hyperlink>
2. This doesn't compile:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding LinkClickedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I get
The type 'Interaction' was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built.
I have package "Microsoft.Xaml.Behaviors.WPF (1.1.39") installed and my namespace is "xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
Thanks
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
You're binding to a property defined in the code-behind of the control. But the DataContext isn't set anywhere, so it will be inherited from the parent. You'll need to use a TemplateBinding to bind to the control's properties:
<Style TargetType="{x:Type local:MaroisHyperlink}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock>
<Hyperlink>
<TextBlock Text="{TemplateBinding LinkText}"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{TemplateBinding LinkClickedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Hyperlink>
</TextBlock>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The Interaction markup looks correct to me. I can't see any reason for that not to compile, unless the NuGet package reference isn't loading correctly. It may be worth raising an issue on the GitHub project for that library[^].
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Had to add the RelativeSource:
<TextBlock Text="{Binding LinkText, RelativeSource={RelativeSource TemplatedParent}}"/>
However that doesn't seem to work in the command (For some reason the command code now compiles):
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{TemplateBinding LinkClickedCommand, RelativeSource={RelativeSource TemplatedParent}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
with
The property 'RelativeSource' was not found in type 'TemplateBindingExtension'.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 30-Nov-22 12:05pm.
|
|
|
|
|
{TemplateBinding x} should be equivalent to {Binding x, RelativeSource={RelativeSource TemplatedParent}} .
The error is telling you that you can't set a RelativeSource on a TemplateBinding .
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
I'd like to use paths for images so I can set the stroke & fill at runtime.
Is it possible to convert PNG or JPG files to XAML?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 29-Nov-22 21:36pm.
|
|
|
|
|
There are a number of tools you can use to convert PNG/JPEG images to XAML. For example, Inkscape[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
How can I create a drop shadow effect all around an element?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Have you tried using a DropShadowEffect[^]?
NB: Avoid the DropShadowBitmapEffect , which has been obsolete since .NET Framework 4 because it was so slow.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|