|
When I instantiate a VM, it typically instantiates the model on its own, which retrieves the data (from a database, xml, or json file).
If the requirements call for modifying/saving the data, I keep the model around in a protected property inside the viewmodel.
Most of the time, I set the data context to the form, and bind controls to the viewmodel(s) and various other properties. I don't think I've ever set the data context to a specific viewmodel because that just means more typing.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
I am working on an app that is organized sort of as a browser with the frame/page construct. I ran across an interesting behavior that I can not find documented. When you use the Initialize event, it gets executed when the page is instantiated. When you use the Loaded event, it gets executed every time the page becomes content of the frame. It makes sense and is sort of alluded to in the Microsoft documentation, but the implication is easily missed as it applies to frame/page.
I have run across other interesting aspects of fame/page that are not clearly documented. Is there a good article or book that really describes the frame/page construct? Everything I have found is rather superficial just giving the highlights or stressing navigation. It should be at least 10 pages long and could be a book of well over 100 pages.
So many years of programming I have forgotten more languages than I know.
|
|
|
|
|
User controls (and Tabs) do the same thing: repeatedly fire the Loaded event. It's not an issue, unless you're initializing in the Loaded event. In those cases, I use an "is Intialized" flag to avoid redundant processing (among other things).
Note that "Loaded" doesn't mean it was being created (every time); it runs every time the user control becomes "active" / available.
This information is only available to those that ferret it out themselves; as you have just done.
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
I like that phrase, "ferret it out". It is a common idiom. I brings to mind the furry little animal that seems to not have a spine and can get into all kinds of places. Per Wikipedia, The name "ferret" is derived from the Latin furittus, meaning "little thief". In this case wiggling around a lot and stealing results from obscure references.
So many years of programming I have forgotten more languages than I know.
|
|
|
|
|
I have a Purchase Order Creation window that has dialogs to allow the user to add items to the PO. The main view model is AddEditPurchaseOrderViewModel. There are different types of items such as Equipment, Lumber, Hardware, etc, that can be added and each has its own View and VM.
In each window the user must select the item they want (Equipment, Hardware, etc) and any number of Sequence Sheets. For each Sequence Sheet selected a new line item created an added to the Purchase Order. So, if the user selects Lumber for 3 Sequence Sheets, then 3 Lumber line items are added to the PO. To create the line items I am cloning the initial line item, changing a few properties on it, and adding it to the PO.
Here's a pic of what I'm working on.
All of a sudden now, my Clone class is throwing bizzare errors. It's telling me that the dialog's viewmodel is not serializable. It doesn't need to be - the entity I get from the VM's is what's being cloned.
Here's my AddPOItem Method
private void AddPOItemExecuted()
{
_PurchaseOrderDialogBase vm = null;
var poItem = new PurchaseOrderItemEntity
{
PurchaseOrderHeaderId = PurchaseOrderHeader.Id,
JobId = PurchaseOrderHeader.JobId,
PurchaseOrderKind = PurchaseOrderHeader.PurchaseOrderKind
};
var args = new AddEditPOItemArgsModel
{
LocationId = SelectedLocation.Id,
POItem = poItem,
SequenceSheets = _sequenceSheets,
POType = PurchaseOrderHeader.PurchaseOrderType
};
switch (PurchaseOrderHeader.PurchaseOrderType)
{
case Entities.Enums.PurchaseOrderType.Equipment:
if (SelectedVendor.Id == AppCore.DNACompany.Id ||
SelectedVendor.Id == AppCore.ParagonCompany.Id)
{
vm = new AddEditEquipmentFromInventoryViewModel(args, "Add Equipment Item");
}
else
{
vm = new AddEditPOItemViewModel(args, "Add Equipment Item", SelectedVendor.Id);
}
break;
case Entities.Enums.PurchaseOrderType.Lumber:
if (SelectedVendor.Id == AppCore.DNACompany.Id ||
SelectedVendor.Id == AppCore.ParagonCompany.Id)
{
vm = new AddEditLumberFromInventoryViewModel(args, "Add Lumber Item");
}
else
{
vm = new AddEditLumberPOItemViewModel(args, "Add Lumber Item");
}
break;
}
if (vm != null)
{
DialogResultEx dialogResult = DialogService.ShowDialog(vm, typeof(MainWindowView));
if ((bool)dialogResult.DialogResult)
{
if (PurchaseOrderHeader.PurchaseOrderType == Entities.Enums.PurchaseOrderType.Equipment ||
PurchaseOrderHeader.PurchaseOrderType == Entities.Enums.PurchaseOrderType.Tools)
{
poItem.EntityState = EntityState.New;
PurchaseOrderHeader.PurchaseOrderItems.Add(poItem);
}
else
{
var selectedSheets = vm.SequenceSheetInfos.Where(x => x.IsSelected).ToList();
foreach (var selectedSheet in selectedSheets)
{
var newItem = poItem.Clone();
newItem.JobSequenceSheetId = selectedSheet.JobSequenceSheetId;
newItem.EntityState = EntityState.New;
newItem.PlanElevation = selectedSheet.JobLotInfo;
PurchaseOrderHeader.PurchaseOrderItems.Add(newItem);
}
}
}
}
}
and my Clone method
public static T Clone<T>(this T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", nameof(source));
}
if (ReferenceEquals(source, null))
{
return default;
}
using (Stream stream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
And here's a pic of the error. Not that the type passed in is PurchaseOrderItemEntity, which IS serializable, yet the exception tells me the AddEditPOItemViewModel is not serializable. Th is doesn't make any sense. I get this regardless of which dialog I opened and the message shows that VM's name.
Anyone have any idea what's going on?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Based on the error, it looks like your PurchaseOrderItemEntity has gained a reference to your AddEditPOItemViewModel instance somehow. You'll need to dig into its properties to find out where that reference is, and mark the reference as "not serialized".
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks, but I already checked into that. No luck.
So this morning, on a hunch, I replaced my clone method with this
public static T Clone<T>(this T source) where T : class
{
var clonedJson = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(clonedJson);
}
and it now works. The previous clone code I posted was working up until something changed this week when it stopped working. Strange.
Thanks for your help.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Overview
I have a DataGrid with a DataGridTemplateColumn with a combobox inside it. The combobox is populated by enums using an ObjectDataProvider. The ComboBox DataTemplate renders the enum items hyperlinks.
Problem
I want to disable hyperlinks that should not be clickable based on some value on the entity. I'm not sure how to go about this.
Here is a screenshot of the grid
and here's the XAML
<DataGridTemplateColumn Header="Actions"
Width="220">
<pre>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Grid.Row="0"
Grid.Column="1"
ItemsSource="{Binding Source={StaticResource actions}}"
Style="{StaticResource comboBoxStyle}"
IsEnabled="True"
Width="200"
Margin="2">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Style="{StaticResource HyperlinkStyle}">
<TextBlock Text="{Binding Converter={StaticResource enumDescConverter}}"
FontSize="14"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ElementName=dispatchControl, Path=DataContext.ExecuteActionCommand}"
CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Hyperlink>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
What's wrong with binding the IsEnabled property on the Hyperlink to a property on your view-model?
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
It would need to be bound to a property on the entity - except there is no entity. I'm loading the combo box with enums.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Use a DataTemplateSelector as the ComboBox.ItemTemplateSelector .
The SelectTemplate() method will be passed each item, (the enum in your case).
Select a data template specific to each case you want.
"Fairy tales do not tell children the dragons exist. Children already know that dragons exist. Fairy tales tell children the dragons can be killed."
- G.K. Chesterton
|
|
|
|
|
I've got Row Details section in a DataGrid. The DataGrid's width is set to 1500, yet the row details content stretches off the right side of the grid. Here's all the XAML.
Here's a screen shot
How do I make the Row Details content wrap?
<DataGrid x:Name="seqSheetDataGrid"
Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding SequenceSheets, Mode=TwoWay}"
SelectedItem="{Binding SelectedSequenceSheet, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
RowDetailsVisibilityMode="Visible"
behaviors:SortDescriptionBehavior.Property="SeqSheetGridSortDescription"
MaxWidth="1700">
<DataGrid.Resources>
<Style TargetType="DataGridCell" BasedOn="{StaticResource dataGridCellStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Removed">
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Foreground" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow"
BasedOn="{StaticResource dataGridRowStyle}">
<Setter Property="BorderBrush" Value="SteelBlue" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="Height" Value="{x:Static sys:Double.NaN}"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.Style>
<Style TargetType="DataGrid"
BasedOn="{StaticResource dataGridStyle}">
<Setter Property="IsReadOnly" Value="True" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding AreFieldsEnabled}" Value="True" />
<Condition Binding="{Binding Job.ContractDate, Converter={StaticResource IsNotNullConverter}}" Value="True" />
<Condition Binding="{Binding Job.ContractDateInitials, Converter={StaticResource IsNotNullConverter}}" Value="True" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="IsReadOnly" Value="False" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<DataGrid.Columns>
<DataGridTextColumn Header="Sequence"
Binding="{Binding Sequence, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="120"
SortDirection="Ascending"
SortMemberPath="Sequence"
IsReadOnly="True"/>
<DataGridTextColumn Header="Lot's / Bldgs"
Binding="{Binding Lot, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="120"
SortMemberPath="Lot"
IsReadOnly="True"/>
<DataGridTextColumn Header="Plan"
Binding="{Binding Plan, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="120"
SortMemberPath="Plan"
IsReadOnly="True"/>
<DataGridTextColumn Header="Square Feet"
Binding="{Binding SquareFeet, StringFormat={}{0:#,#}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="120"
HeaderStyle="{StaticResource dataGridRightAlignColHeaderStyle}"
SortMemberPath="SquareFeet"
IsReadOnly="True">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Right" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Elevation"
Binding="{Binding Elevation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="120"
SortMemberPath="Elevation"
IsReadOnly="True"/>
<DataGridComboBoxColumn Header="Garage Type"
SelectedValueBinding="{Binding GarageTypeId}"
SelectedValuePath="Id"
DisplayMemberPath="Caption"
Width="120"
SortMemberPath="GarageTypeId"
IsReadOnly="True">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding ElementName=jobView, Path=DataContext.GarageTypes}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding ElementName=jobView, Path=DataContext.GarageTypes}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridTextColumn Header="Enhanced Rear"
Binding="{Binding EnhancedRear, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource TrueFalseYesNoConverter}}"
Width="120"
SortMemberPath="EnhancedRear"
IsReadOnly="True"/>
<DataGridTextColumn Header="Enhanced Left"
Binding="{Binding EnhancedLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource TrueFalseYesNoConverter}}"
Width="120"
SortMemberPath="EnhancedLeft"
IsReadOnly="True"/>
<DataGridTextColumn Header="Enhanced Right"
Binding="{Binding EnhancedRight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource TrueFalseYesNoConverter}}"
Width="120"
SortMemberPath="EnhancedRight"
IsReadOnly="True"/>
<DataGridTextColumn Header="Address"
Binding="{Binding Address, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="400"
SortMemberPath="Address"
IsReadOnly="True"/>
<DataGridTextColumn Header="Status"
Binding="{Binding Status, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="150"
SortMemberPath="Status"
IsReadOnly="True"/>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Options: "
FontWeight="Bold"
Margin="2" />
<TextBlock Text="{Binding OptionsText}"
TextWrapping="Wrap"
Margin="2" >
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Removed">
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Foreground" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 29-Jul-21 13:48pm.
|
|
|
|
|
Is it possible to get the MainWindow from the exeucting assembly?
I am opening a dialog contained in another assembly and in that dialog I want to set the Owner to the Main Window of my executing app.
I know in the dialog I can do
var callingAssembly = Assembly.GetCallingAssembly();
which gives me the assembly of my main app. But Can I then somehow get access to that assembly's Application.Current.MainWindow?
What I'd really like, in my dialog, is something like this:
public MyDialog()
{
var window =
this.Owner = window;
}
Thanks!
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 23-Jul-21 15:42pm.
|
|
|
|
|
Assuming you're not using a separate AppDomain to load the assembly, Application.Current.MainWindow should still work from the other assembly.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
OK, that worked. Thanks
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
|
|
Slacker007 wrote: Not sure why you would not use Visual Studio Community Edition to manage/edit your WPF files and projects.
Perhaps Mike is secretly part of a large, enterprise development team.
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
What I'm looking for is a control I can slip into my app. Guess I didn't make that plain.
The less you need, the more you have.
Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?
JaxCoder.com
|
|
|
|
|
Sorry, Mike. I did not notice.
|
|
|
|
|
Looks like the SmithHtmlEditor project has moved to GitHub:
GitHub - adambarath/SmithHtmlEditor: The source code is about html editor usage in WPF application.[^]
It doesn't seem to have much detail there, and is still pointing to the (now defunct) CodePlex archive for more information. But the GitHub version seems to have been updated more recently.
There don't seem to be a huge number of other options - there's this one[^] from 2015, and this one[^], also from 2015, but beyond that Google is only suggesting commercial products.
I've not tried it, but you might have better luck embedding a WebView2[^] control and using a Javascript HTML editor.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks Richard,
I considered the CodeProject one but settled on the smithhtmleditor. Seems to be working Ok, a little quirky with fonts but other than that fairly easy to use. All I need is some basic editing for keeping notes in the app I'm working on.
The less you need, the more you have.
Even a blind squirrel gets a nut...occasionally.
JaxCoder.com
|
|
|
|
|
I'm working on a WPF app that we now want to have a 'Test Mode'.
Right now, I have an 'IsTestMode' property in the app.config. If I set it to True, then the login window's border is red, and there's a red TEST MODE banner across the top of the main window. The Test Mode copy is installed in a different location then the production copy.
In the code behind of the Main Window I have:
Uri iconUri = null;
if (AppCore.IsTestMode)
{
iconUri = new Uri("my_test.ico", UriKind.Relative);
}
else
{
iconUri = new Uri("my.ico", UriKind.Relative);
}
this.Icon = BitmapFrame.Create(iconUri);
This changes the Taskbar Icon, not the desktop icon
This all works fine.
Now I want to add an installer project, but I'm stuck on how to make the Test Mode changes during installation. I'd like to do the following during installation and not let the user change them:
- Set the installation location
- Set the IsTestMode setting
- Change the desktop icon.
Can anyone shed some light on how to do this?
Thanks
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 19-Jul-21 15:14pm.
|
|
|
|
|
I have a user control with a custom Routed Event. I'm trying to pass event args:
public class NavigationViewFilterEventArgs : RoutedEventArgs
{
public List<LookupEntity> StatusFilters { get; private set; }
public List<LookupEntity> FilterItems { get; private set; }
public NavigationViewFilterEventArgs(RoutedEvent e, List<LookupEntity> statusFilters, List<LookupEntity> filterItems) :
base(e)
{
StatusFilters = statusFilters;
FilterItems = filterItems;
}
}
Here's the UserControl code behind:
public static readonly RoutedEvent FilterExecutedEvent =
EventManager.RegisterRoutedEvent("FilterExecuted",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(NavigationView));
public event RoutedEventHandler FilterExecuted
{
add { AddHandler(FilterExecutedEvent, value); }
remove { RemoveHandler(FilterExecutedEvent, value); }
}
private void RaiseFilterExecutedEvent()
{
var args = new NavigationViewFilterEventArgs(FilterExecutedEvent, StatusFilters, FilterItems);
RaiseEvent(args);
}
The control is on the Main Window:
<ctrls:NavigationView Grid.Row="7"
Caption="Companies"
StatusFilters="{Binding DataContext.CompanyStatusFilterItems,
ElementName=window, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="FilterExecuted">
<i:InvokeCommandAction Command="{Binding DataContext.CompanyFilterExecutedEventCommand,
ElementName=window}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ctrls:NavigationView>
And finally the handler in the Main Window view model:
private ICommand _CompanyFilterExecutedEventCommand;
public ICommand CompanyFilterExecutedEventCommand
{
get
{
if (_CompanyFilterExecutedEventCommand == null)
_CompanyFilterExecutedEventCommand = new RelayCommand<NavigationViewFilterEventArgs>(p => CompanyFilterExecutedEventExecuted(p), p => CompanyFilterExecutedEventCanExecute());
return _CompanyFilterExecutedEventCommand;
}
}
private bool CompanyFilterExecutedEventCanExecute()
{
return true;
}
private void CompanyFilterExecutedEventExecuted(NavigationViewFilterEventArgs args)
{
}
When I run this and click the button, the event reaches the Main Window VM's CompanyFilterExecutedEventExecuted method, but the param is always null.
I could use some help here.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
|