|
Kevin Marois wrote: The first column, the Type, is read only. The rest are read only
Unless you meant to say that all columns are read-only, I suspect the rest are editable.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Yup, you're right. I corrected it.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
BindingBase.StringFormat Property (System.Windows.Data) | Microsoft Docs
"Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I
|
|
|
|
|
Thanks for the reply, but I don't see how that solves the issue
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I got a new UI change in the app where a given UI (it contains a tree-list-view) can now be split (very much like VS text editor), in 2 identical view (same view model, same view template).
I need to track which of the 2 split view is the "current one", so if the view model drive an edit session of one of the cell, only the "current split view" will start editing.
Now the question is.. how do I know which one is the "current view"?!
EDIT, SOLVED IT!
Using the power of friendship! And, erm... MultiBinding , against both model.IsEditing and "is in active view ".
modified 5-Apr-22 21:23pm.
|
|
|
|
|
As part of my own framework I am developing Service and View Model Locators. Both are done, but I have one problem.
Here's what I have so far:
Interface
public interface IViewModelLocator
{
object Get<T>();
void Register(Type viewType, object viewModel);
}
View Model Locator Class
public class ViewModelLocator : IViewModelLocator
{
private IDictionary<Type, object> _storage;
public ViewModelLocator()
{
_storage = new Dictionary<Type, object>();
}
public object Get<T>()
{
if (_storage.ContainsKey(typeof(T)))
{
return _storage.FirstOrDefault(x => x.Key == typeof(T)).Value;
}
else
{
throw new Exception($"The requested view '{typeof(T)}' is not registered");
}
}
public void Register(Type viewType, object viewModel)
{
if (!_storage.ContainsKey(viewType))
{
_storage.Add(viewType, viewModel);
}
else
{
throw new Exception($"An instance of the view model '{viewModel}' is already registered for the view '{viewType}'");
}
}
}
App Setup
public partial class App : Application
{
public static IServiceLocator ServiceLocator { get; private set; }
public static IViewModelLocator ViewModelLocator { get; private set; }
public App()
{
ServiceLocator = new ServiceLocator();
ServiceLocator.Register(typeof(IMyService), new MyService());
ViewModelLocator = new ViewModelLocator();
ViewModelLocator.Register(typeof(MainWindowView), new MainWindowViewModel(ServiceLocator.Get<IMyService>()));
ViewModelLocator.Register(typeof(LoginView), new LoginViewModel());
}
}
Window Code Behind
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
this.DataContext = App.ViewModelLocator.Get<MainWindowView>();
}
}
Problem
Everything to this point works fine, except that I don't want to have to call the VM locator in code in the code behind. I would like to use it in XAML. I have seen other approaches where they do this:
x:Class="My.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
DataContext="{Binding Source={StaticResource MyViewModelLocator}, Path=Main}">
There are 2 problems here.
- It required a resources entry in App.XAML:
<Application.Resources>
<core:ViewModelLocator x:Key="MyViewModelLocator" />
</Application.Resources> - It requires a property on the VM Locator called "Main". Since these classes are going to be in my framework, there won't be a property for each ViewModel that other apps use on the VM Locator.
What I'd Like
It would be nice if I could do something like:
DataContext="{Binding Path="{x:static TestApp:App.ViewModelLocator.Get<???????>()}"
where the question marks represent the type of the window it's in.
Is there any way to this in XAML?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 5-Apr-22 14:06pm.
|
|
|
|
|
You can't reference generic class in Xaml... However I think you can do something!
How about something like that (pseudo code that might be inspiring)?
DataContext="{Binding Converter={x:Static TestApp:App.ViewModelConverter}, ConvertParameter={x:Type local:MyVMType}}"
Use a Converter with a ConverterParameter?
|
|
|
|
|
Kevin Marois wrote:
public object Get<T>()
{
if (_storage.ContainsKey(typeof(T)))
{
return _storage.FirstOrDefault(x => x.Key == typeof(T)).Value;
}
else
{
throw new Exception($"The requested view '{typeof(T)}' is not registered");
}
} That's a very strange way of using a dictionary. The alternative is simpler and significantly more efficient:
public object Get<T>()
{
if (!_storage.TryGetValue(typeof(T), out var result)) return result;
throw new ArgumentException($"The requested view '{typeof(T)}' is not registered");
}
And the method doesn't have to be generic - you can have a non-generic overload as well:
public object Get(Type viewType)
{
if (viewType is null) throw new ArgumentNullException(nameof(viewType));
if (!_storage.TryGetValue(viewType, out var result)) return result;
throw new ArgumentException($"The requested view '{viewType}' is not registered", nameof(viewType));
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
How about a custom markup extension[^]?
public class LocateViewModelExtension : MarkupExtension
{
public Type ViewType { get; set; }
private Type ResolveViewType(IServiceProvider serviceProvider)
{
if (ViewType != null) return ViewType;
var rootObjectProvider = (IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider));
return rootObjectProvider?.RootObject?.GetType();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
Type viewType = ResolveViewType(serviceProvider);
if (viewType is null) return null;
if (App.ViewModelLocator is null) return null;
return App.ViewModelLocator.Get(viewType);
}
} Usage:
DataContext="{LocateViewModel ViewType={x:Type my:MainView}}" or simply:
DataContext="{LocateViewModel}" If you don't specify a ViewType , it should be able to use the type of the root object for the XAML file it's used in.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thank you for both your answers. Working great now.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I want to develop a WPF application and then develop an Ionic application for mobile. I'm planning to create an API for an Ionic application that uses the same database. Is it feasible?
Should I create an API and use it for both WPF and Ionic applications?
|
|
|
|
|
Databases are client agnostic. They don't care what language the client is written in or even what type of app it is.
|
|
|
|
|
Create a web API which services both client applications.
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
I'm trying to implement show/hide password textboxes.
There is a PasswordBox and a TextBox both in the same grid cell, and a checkbox off to the right. When I check it, nothing happens. I get no binding errors. Anyone see what's wrong?
<pre><br />
<Grid Grid.Row="6"
Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<PasswordBox Grid.Row="0"
Grid.Column="0"
TabIndex="2"
cls:PasswordBoxAssistant.BindPassword="True"
cls:PasswordBoxAssistant.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="Visible"
Width="400">
<PasswordBox.Style>
<Style TargetType="PasswordBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chkShowPassword, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</PasswordBox.Style>
</PasswordBox>
<TextBox Grid.Row="0"
Grid.Column="0"
Text="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TabIndex="2"
Width="400"
Visibility="Hidden">
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{StaticResource textBoxStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chkShowPassword, Path=IsCheckd}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<CheckBox Grid.Row="0"
Grid.Column="1"
x:Name="chkShowPassword"
HorizontalAlignment="Right"
VerticalAlignment="Center"
ToolTip="Show Password"
IsChecked="{Binding IsShowPasswordChecked}"
Style="{StaticResource showPasswordCheckBoxStyle}"/>
</Grid>
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
You don't have anything that submits the username and password. There is no "login" button.
But, figuring out if the credentials are correct is not done in the XAML. It's done in the class your XAML is bound to, which you also didn't show.
Sorry, I completely misread the question.
modified 7-Mar-22 18:22pm.
|
|
|
|
|
The Visibility property set directly on the control overrides the value set by the style.
You also have the trigger on the password box the wrong way round - you want to hide it when the checkbox is checked, not unchecked.
And you have a typo in the binding for the textbox - IsCheckd instead of IsChecked .
Change your code to:
<PasswordBox
Grid.Row="0"
Grid.Column="0"
TabIndex="2"
cls:PasswordBoxAssistant.BindPassword="True"
cls:PasswordBoxAssistant.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="400"
>
<PasswordBox.Style>
<Style TargetType="PasswordBox">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chkShowPassword, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</PasswordBox.Style>
</PasswordBox>
<TextBox
Grid.Row="0"
Grid.Column="0"
Text="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TabIndex="2"
Width="400"
>
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{StaticResource textBoxStyle}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chkShowPassword, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
That did it.
Thank you
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
The idea with a collection that implements INotifyCollectionChanged , is that it will be (possibly) be used in an ItemsControl , which will get the CollectionViewSource.GetDefaultView(Object) for it, that is likely to be a ListCollectionView .
So far... so good...
However, as I just discovered today , when using AddRange() , RemoveRange() , i.e. creating Add/Remove events with more that 1 object will trigger the following error in the ListCollectionView :
System.NotSupportedException: Range actions are not supported.
Stack Trace:
ListCollectionView.ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e)
ListCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
AbstractInvocation.Proceed()
Invocation.CallBase() line 147
ReturnBaseOrDefaultValue.Execute(Invocation invocation) line 88
IInterceptor.Intercept(Invocation invocation) line 34
Interceptor.Intercept(IInvocation underlying) line 113
AbstractInvocation.Proceed()
NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
Mmm... unfortunate...
I can think of 2 possible fixes and I am undecided as to which one to use?!
- When a range
Add/Remove event if fired, instead fire a Reset event. - When a range
Add/Remove event if fired, instead fire multiple Add/Remove single item events. - or.. mmm... have a FireEventMode property, which would let the user decide whether to use event as is, split it, or fire Reset.
Wo.. what say you?
|
|
|
|
|
|
I have a DataGrid where we display an animation on DataGridRows when a property in the ItemSource is set.
If the height of the DataGrid is enough low to display the VerticalScrollBar, I can scroll up and down and see the animations jump to the wrong rows.
I have made code snippet that reproduce the issue:
* The code shuld be "copy-paste".
* The DataGrid displays a list of Persons.
* If a person's HasBirthday = true the corresponding DataGridRow displays the animation.
* The first item in the list has HasBirthday = true .
To reproduce
1) Notice the first row has the animation running already.
2) Click the second row in the Grid.
3) Scroll down.
3) Some other row should now also have the animation on it.
Scrolling up and down a couple of times should also diplay the issue.
* Any explanation why this happens?
* Any suggestions how to fix this?
/BR
Steffe
XAML
<Window x:Class="Main.Views.DataGridCustomAnimation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Main.Views"
mc:Ignorable="d"
Loaded="Window_Loaded"
Title="DataGridCustomAnimation" Height="450" Width="800">
<Window.Resources>
<Style TargetType="{x:Type DataGridRow}" x:Key="DataGridRowSmallStyle">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Background" Value="LightGray" />
<Setter Property="MinHeight" Value="26" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Gray" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid x:Name="grid"
Grid.Row="0"
AutoGenerateColumns="True">
<DataGrid.Resources>
<Style BasedOn="{StaticResource DataGridRowSmallStyle}" TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasBirthday}" Value="True" />
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="False" />
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(DataGridRow.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0:0:0.0" Value="#ca0516"/>
<EasingColorKeyFrame KeyTime="0:0:1.0" Value="#ca0516"/>
<EasingColorKeyFrame KeyTime="0:0:2.0" Value="#db552c"/>
<EasingColorKeyFrame KeyTime="0:0:3.0" Value="#ca0516"/>
</ColorAnimationUsingKeyFrames>
<!-- Note: -->
<!-- Cannot set the Foreground to white with a 'setter' when using an animation. -->
<ColorAnimation Storyboard.TargetProperty="(DataGridRow.Foreground).(SolidColorBrush.Color)"
From="White"
To="White" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
<Button Grid.Row="2" Height="40" Width="40" Content="Test" Click="Test_Clicked" />
</Grid>
</Window> C# code behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Main.Views
{
public partial class DataGridCustomAnimation : Window
{
public DataGridCustomAnimation()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var list = new List<Person>();
for (int i = 0; i < 40; i++)
{
list.Add(new Person
{
Name = $"Person {i}"
});
}
list.First().HasBirthday = true;
grid.ItemsSource = list;
}
private void Test_Clicked(object sender, RoutedEventArgs e)
{
var hasBirthdays = (grid.ItemsSource as List<Person>).Where(x => x.HasBirthday);
Console.WriteLine(hasBirthdays.Count());
}
}
public class Person
{
public string Name { get; set; }
public bool HasBirthday { get; set; }
public bool IsDead { get; set; }
}
}
|
|
|
|
|
I would just change the color of the row. Cost versus benefit.
"Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I
|
|
|
|
|
Hi!
Sorry I don't understand.
Can you please explain what you mean?
|
|
|
|
|
I am trying to print labels with a height of 38mm. The number in a run can be from 1 to 2000. The preview like in a document viewer would be desirable. The label height is entered in the printer's print settings. The printer is not the default printer in the user profile. In the DocumentViewer, however, the print settings are only loaded if it is the default printer.
I want to specify the printer from a CONFIG file and the label height must be 38mm, not DIN A4!
I can't get any further without a program example. Thank you for help.
|
|
|
|
|
|
Hey all I have the following OnStartup for my WPF desktop app that goes to a local directory and gathers all the images within that folder (box1 being the example code below):
<pre lang="C#">public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var imageSource = new ImageSource(Path.Combine(@"C:\photos\boxes\", "box1"), TimeSpan.FromHours(1));
var viewModel = new MainWindowViewModel(imageSource);
var window = new MainWindow()
{
DataContext = viewModel
};
window.Closed += (s, a) => { viewModel.Dispose(); };
window.Show();
}
}
This works just fine for the component I have on the MainWindow.xaml that's label box1 but the other boxes 2-9 do not load their own images from their respective folders - they all show the same images as box1 has.
The structure of the directory is this:
C:\
|-photos\
|boxes\
|box1
|box2
|box3
|box4
|box5
|box6
|box7
|box8
|box9
|box10
On the MainWindow I have this code that allows all those photos from each directory into its own element:
<Window.Resources>
<FluidKit:SlideTransition x:Key="SlideTransition" x:Shared="False"/>
<FluidKit:CubeTransition x:Key="CubeTransition" Rotation="BottomToTop" x:Shared="False"/>
<FluidKit:FlipTransition x:Key="FlipTransition" x:Shared="False"/>
<local:ImageSourceConverter x:Key="ImageSourceConverter"/>
<DataTemplate x:Key="ItemTemplate" x:Shared="False">
<Image Source="{Binding Path, Converter={StaticResource ImageSourceConverter}}"
Stretch="Fill"/>
</DataTemplate>
</Window.Resources>
<Grid>
<FluidKit:TransitionPresenter RestDuration="0:0:3"
IsLooped="True"
Transition="{StaticResource FlipTransition}"
ItemsSource="{Binding box1}"
Width="357"
Height="272"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplate="{StaticResource ItemTemplate}"
x:Name="box1"
Margin="0,0,0,454"/>
<FluidKit:TransitionPresenter RestDuration="0:0:3"
IsLooped="True"
Transition="{StaticResource FlipTransition}"
ItemsSource="{Binding box2}"
Width="357"
Height="272"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplate="{StaticResource ItemTemplate}"
x:Name="box2"
Margin="357,0,0,0"/>
ETC.......
Like I said above, this first element ItemsSource="{Binding box1} loads the images as they should be from the box1 directory just fine but all the other 2-9 after that are loaded the same images as box1.
The mainWindowViewModel code looks like this:
public class MainWindowViewModel : IDisposable
{
private readonly IDisposable _token1;
private readonly IDisposable _token2;
...ETC
public MainWindowViewModel(IImageSource imageSource)
{
_token1 = imageSource
.GetImages().ObserveOn(DispatcherScheduler.Current)
.Subscribe(i => UpdateImages(i, box1), ex => ShowError(ex));
_token2 = imageSource
.GetImages().ObserveOn(DispatcherScheduler.Current)
.Subscribe(i => UpdateImages(i, box2), ex => ShowError(ex));
ETC...
}
private void ShowError(Exception ex)
{
MessageBox.Show(ex.Message, "Photo Gallery", MessageBoxButton.OK, MessageBoxImage.Error);
}
private void UpdateImages(IEnumerable<string> images, ObservableCollection<ImageViewModel> animation)
{
animation.Clear();
foreach (var i in images)
{
animation.Add(new ImageViewModel { Path = i });
}
}
public ObservableCollection<ImageViewModel> box1 { get; set; } =
new ObservableCollection<ImageViewModel>();
public ObservableCollection<ImageViewModel> box2 { get; set; } =
new ObservableCollection<ImageViewModel>();
ETC...
public void Dispose()
{
_token1.Dispose();
_token2.Dispose();
ETC...
}
}
The function that loops to get each file image within the directory is this:
public class ImageSource : IImageSource
{
private readonly string _path;
private readonly TimeSpan _interval;
public ImageSource(string path, TimeSpan interval)
{
_path = path;
_interval = interval;
}
public IObservable<IEnumerable<string>> GetImages()
{
if (!Directory.Exists(_path))
{
return Observable.Empty<IEnumerable<string>>();
}
return Observable.Create<IEnumerable<string>>(observer =>
{
return TaskPoolScheduler.Default.ScheduleAsync(async (ctrl, ct) =>
{
for (;;)
{
if (ct.IsCancellationRequested)
{
break;
}
try
{
var images = Directory.GetFiles(_path, "*.jpg");
if (images.Count() > 9)
{
observer.OnNext(images.PickRandom());
}
}
catch (Exception ex)
{
observer.OnError(ex);
throw;
}
await ctrl.Sleep(_interval).ConfigureAwait(false);
}
});
});
}
The code currently goes to each of the ObservableCollection<imageviewmodel> box[X] { get; set; } and sets the path to each image within that folder. Box2-10 are the same files as box 1 of course.
How can I modify that onStartup() code to allow for it to consume each box folders' files and place them into the appropriate box # component instead of just using that box1 files?
Thanks!
|
|
|
|
|