In page edit mode I want to show a read-only text that is based on a page property value. The text could for example be "A content review reminder email will be sent 2015-10-10", where the date is based on the page published date + six months (a value that will be configurable and therefore can change anytime). So far I've tried to accomplish something like this by adding another property on the page.
I've added the property CurrentReviewReminderDate to an InformationPage class we use. In page edit mode the property name is shown, but it doesn't have a value. How do I do to show the value in page edit mode (preferably as a label)?
[CultureSpecific]
[Display(
Name = "Review reminder date",
Description = "On this date a reminder will be sent to the selected mail to remember to verify page content",
Order = 110)]
[Editable(false)]
public virtual string CurrentReviewReminderDate
{
get
{
var daysUntilFirstLevelReminder =
int.Parse(WebConfigurationManager.AppSettings["PageReviewReminder_DaysUntilFirstLevelReminder"]);
if (CheckPublishedStatus(PagePublishedStatus.Published))
{
return StartPublish.AddDays(daysUntilFirstLevelReminder).ToString();
}
return "";
}
set
{
this.SetPropertyValue(p => p.CurrentReviewReminderDate, value);
}
}
EPiServer internally uses the GetPropertyValue method (i.e. the opposite of SetPropertyValue) when retrieving content for the UI.
This makes sense, otherwise your "made-up" value would be stored as the real value whenever the content is saved. This would make fall-back values etc impossible to implement.
So, this is by-design (and quite wisely so) in EPiServer. :)
However, you can customize how properties work by:
Using custom editors by applying UI hints
Modifying property metadata (for example, to display a generated value as a watermark in a textbox without interfering with the actual value being saved)
I could be misunderstanding what you're trying to do, but off the top of my head it looks like a custom editor could be a viable option for your use case?
Another solution would be to hook into the LoadedPage-event and add the value from there. This might not be the best way performance-wise since you need to do a CreateWritableClone, but depending on the site it might not matter.
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class EventInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
ServiceLocator.Current.GetInstance<IContentEvents>().LoadedContent += eventRegistry_LoadedContent;
}
void eventRegistry_LoadedContent(object sender, ContentEventArgs e)
{
var p = e.Content as EventPage;
if (p != null)
{
p = p.CreateWritableClone() as EventPage;
p.EventDate = p.StartPublish.AddDays(10);
e.Content = p;
}
}
}
Related
Suppose POLine has a UsrCustomfield and also have some calculation to populate the value for the UsrCustomfield based on other fields in the same POLine.
Which event handlers are used to populate and save the value to the DB? Saving has to be done from Graph itself... But from where?
You have multiple ways to calculate the value of a custom field.
The first method is using a PXFormula attribute. This can work well if you have an easy query to calculate the value of the field. This would be set on the DAC extension of POLine that you declared the UsrCustomfield as an attribute. The help article has many different ways to accomplish this.
https://help-2018r1.acumatica.com/Wiki/(W(59936))/ShowWiki.aspx?pageid=1d25dc74-747c-4d44-8ad9-033e5a476b6f
Another way to accomplish this is trigger events based on the fields, or row, updating to re-calculate. For example, if you want to re-calculate every time the row is updated with ANY field, you can use the function:
public virtual void POLine_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e, PXRowUpdated del)
{
//first, invoke the base method. can be placed anywhere in your code.
del?.Invoke(sender, e);
//get the row
POLine line = (POLine)e.Row;
if (line == null) return; //always good to check if the object is not null
//get the DAC extension from the object
POLineExt lineExt = line.GetExtension<POLineExt>();
decimal? CalculatedValue = 0;
//do some calculation to figure out what the value should be
//compare the new value against the existing value
if(lineExt.UsrCustomField != CalculatedValue)
{
//set the new value
sender.SetValueExt<POLineExt.usrCustomField>(line, CalculatedValue);
}
}
The issue with this is that it will call the code every time a POLine is updated. This may be too many calls. You can use functions to update each individual line using a FieldUpdated event on each field to recalculate as well. For example, you may want to check ExtCost:
public virtual void POLine_ExtCost_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e, PXFieldUpdated del)
{
//similar code as above
}
This method is more code, and more complex, but may be needed if your logic cannot fit into a PXFormula attribute.
I have a "long-running" WPF desktop application which uses NLog and the NLogViewer to display runtime status. It is used to monitor a database, pick up requests for processing, and then logging the results. (Yeah -- should have been a service, but we wanted to start out this way first -- baby steps.) The results are logged to a text log file and the NLogViewer shows the current items in the log in real time.
After the app has been running several hours, I've noticed I can only view a portion of the log -- from current entry back to an hour or so earlier. The scrollbar lets me scroll down to the most current entry, but stops way before the first entry. At this time, there isn't a lot of logging going on, i.e., no debugging or tracing, just Info, Warnings, and Errors, so I don't think the size of the log would have an impact.
Is there a way to have the NLogViewer let me view the entire log file or will it only show me a limited window into the log? Is there an alternate NLog viewer WPF control? I'm not really looking at a standalong NLog viewer at this time. If you need more information, please let me know.
Thanks!
Edit: I had some time to revisit this issue and got the NLogViewer source from GitHub. In the code-behind for the view, there is a hard coded value of 50 for the number of entries held in the items source for the ListView. For each item over 50, an item is removed from the beginning of the log. I could, of course, download the source and modify it to my liking which might be the final solution as the project hasn't been updated in 3 years. However, I might try and see if I can create a wrapper around the original control and allow for some basic extensions such as a configurable number of log entries to display and to always show the last entry added. If I have any success, I'll post what I did.
I made a new version of the NLogViewer. Without any limitation. You can also change the style if you want to
https://www.nuget.org/packages/Sentinel.NLogViewer
https://github.com/dojo90/NLogViewer
I spend a lot of time yesterday working on this, trying to just extend the existing class with new behaviors or properties. The biggest issue was with the hard coded 50 entries in a protected method that I couldn't access or override. And since the NLogViewer control is defined with XAML, I couldn't easily subclass it with a "non-XAML" class (compile errors). In the end, it seemed simpler to just use the original source code and make some mods to get the desired results.
I made the following changes:
I added 2 properties to the NLogViewer "view":
[Description("Automatically scroll last entry into view?"), Category("Data")]
public bool ScrollIntoView { get; set; } = true;
[Description("How many log entries should be kept in memory."), Category("Data")]
public int VisibleEntryCount { get; set; } = 50; // Original code default
These will control if the last log entry is scrolled into view and how many log entries will be available for display.
I modified the existing LogReceived handler to use the VisibleEntryCount property.
protected void LogReceived(NLog.Common.AsyncLogEventInfo log)
{
LogEventViewModel vm = new LogEventViewModel(log.LogEvent);
if (this.VisibleEntryCount == 0)
{
this.VisibleEntryCount = 50; // Original code default.
}
this.Dispatcher.BeginInvoke(new Action(() =>
{
if (this.LogEntries.Count >= this.VisibleEntryCount)
{
// We've reached our limit.
this.LogEntries.RemoveAt(0);
}
this.LogEntries.Add(vm);
}));
}
And I added a new event handler for whenever a new log entry is added. This will let me ensure the last entry is always visible (if specified by the user).
private void LogEntries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.ScrollIntoView)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (this.logView != null)
{
int count = this.LogEntries.Count;
object selectedItem = (count > 0) ? this.LogEntries[count - 1] : null;
if (selectedItem != null)
{
this.logView.Dispatcher.BeginInvoke((Action)(() =>
{
this.logView.UpdateLayout();
this.logView.ScrollIntoView(selectedItem);
}));
}
}
}
}
}
It seems to be working OK. As always, I'm open to additional solutions or suggestions. Thanks!
Is it possible to programmatically determine if the content of a block uses personalization?
I need to know in order to handle caching correctly when the content of a block can be personalized for different visitor groups.
In order to make decision about personalization you will need to get access to ContentAreaItem instance itself.
The most easiest way to get access to ContentAreaItem is to hook into rendering pipeline. You may need to swap out ContentAreaRenderer (see AlloyTech sample site for code snippet) with your own implementation.
Then you can implement BeforeRenderContentAreaItemStartTag:
public class MyCustomContentAreaRenderer : ContentAreaRenderer
{
protected override void BeforeRenderContentAreaItemStartTag(TagBuilder tagBuilder, ContentAreaItem contentAreaItem)
{
var isPersonalizationApplied = contentAreaItem.AllowedRoles.Any();
}
}
Next question of course is how you will deliver this decision down to block type instance, as overridden ContentAreaRenderer method is far enough from the block instance inside content area.
#keithl8041, you can use VisitorGroupHelper to check whether current user matches particular role:
using EPiServer.Personalization.VisitorGroups;
using EPiServer.Security;
protected override void BeforeRenderContentAreaItemStartTag(TagBuilder tagBuilder, ContentAreaItem contentAreaItem)
{
var groupHelper = new VisitorGroupHelper();
var isPersonalizationApplied = contentAreaItem.AllowedRoles.Any();
var isCurrentUserInAnyOfGroups = contentAreaItem.AllowedRoles.Any(r => groupHelper.IsPrincipalInGroup(PrincipalInfo.CurrentPrincipal, r);
}
first I must apologize because english isn't my mother language, but I'll try to be clear in what I'm asking.
I have a set of rows in a tableview, every row has diferent comboboxs per columns. So, the interaction between combobox must be per row. If in the Combobox A1, I select Item 1, in the Combobox A2 the itemlist will be updated.
My problem is that every combobox A2, B2, C2, etc. Is being updated according the choice in A1... same thing with B1,C1 combobox.
I need to updated just the A2, according to A1. B2 according to B1, etc.
I set the comboboxes by cellfactory, because I have to save the data from behind in a serializable object.
Hope is clear.
Regards.
This is pretty much a pain...
From a TableCell, you can observe the TableRow via it's tableRowProperty().
From the TableRow, you can observe the item in the row, via the table row's itemProperty().
And of course, from the item in the row, you can observe any properties defined in your model class, and update a list of items in the combo box accordingly.
The painful part is that any of these value can, and will at some point change. So the things you need to observe keep changing, and you have to manage adding and removing listeners as this happens.
The Bindings.select method is supposed to help manage things like this, but as of JavaFX 8, it prints huge stack traces to the output as warnings whenever it encounters a null value, which it does frequently. So I recommend doing you own listener management until that is fixed. (For some reason, the JavaFX team doesn't seem to consider this a big deal, even though encountering null values in the path defined in a Bindings.select is explicitly supported in the API docs.)
Just to make it slightly more unpleasant, the getTableRow() method in TableCell<S,T> returns a TableRow, instead of the more obvious TableRow<S>. (There may be a reason for this I can't see, but, well...). So your code is additionally littered with casts.
I created an example that works: apologies for it being based on US geography, but I had much of the example already written. I really hope I'm missing something and that there are easier ways to do this: please feel free to suggest something if anyone has better ideas.
On last note: the EasyBind library may provide a simpler way to bind to the properties along an arbitrary path.
As #James_D's example no longer runs due to link rot, and I was dealing with this same issue, here's how I figured out to create this effect.
View the full test case here.
I extend the builtin ComboBoxTableCell<S, T> to expose necessary fields. The custom TableCell has a Supplier<S> tableValue = (S) this.getTableRow().getItem(); used to access the applicable Data object. Additionally, I reflectively retrieve and store a reference to the cell's ComboBox. Because it is lazily instantiated in the superclass, I also have to set it via reflection before I can get it. Finally, I have to initialize the ComboBox as well, as it would be in javafx.scene.control.cell.CellUtils.createComboBox, since I'm manually creating it. It is important to expose these, as:
In the column's CellFactory, we finish initializing the ComboBoxCell. We just need to create a new instance of our custom ComboBoxTableCell and then when the comboBox is shown for the first time (e.g. we can be sure that we have a Data object associated with the cell), we bind the ComboBox#itemsProperty to a Bindings.When returning the proper ObservableList for the case.
CellFactory:
column1.setCellFactory(c -> {
TransparentComboBoxTableCell<Data, Enum> tcbtc = new TransparentComboBoxTableCell<>();
tcbtc.comboBox.setOnShown(e -> {
if (!tcbtc.comboBox.itemsProperty().isBound()) tcbtc.comboBox.itemsProperty().bind(
Bindings.when(tcbtc.tableValue.get().base.isEqualTo(BASE.EVEN)).then(evens).otherwise(
Bindings.when(tcbtc.tableValue.get().base.isEqualTo(BASE.ODD)).then(odds).otherwise(
FXCollections.emptyObservableList()
))
);
});
return tcbtc;
});
custom ComboBoxTableCell:
public static class TransparentComboBoxTableCell<S, T> extends ComboBoxTableCell<S, T> {
public TransparentComboBoxTableCell() {
this(FXCollections.observableArrayList());
}
public TransparentComboBoxTableCell(ObservableList<T> startingItems) {
super(startingItems);
try {
Field f = ComboBoxTableCell.class.getDeclaredField("comboBox");
f.setAccessible(true);
f.set(this, new ComboBox<>());
comboBox = (ComboBox<T>) f.get(this);
// Setup out of javafx.scene.control.cell.CellUtils.createComboBox
// comboBox.converterProperty().bind(converter);
comboBox.setMaxWidth(Double.MAX_VALUE);
comboBox.getSelectionModel().selectedItemProperty().addListener((ov, oldValue, newValue) -> {
if (this.isEditing()) {
this.commitEdit((T) newValue);
}
});
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
throw new Error("Error extracting 'comboBox' from ComboBoxTableCell", ex);
}
tableValue = () -> (S) this.getTableRow().getItem();
}
public final ComboBox<T> comboBox;
public final Supplier<S> tableValue;
}
Is there an easy way to set the Localizable property to true for newly created usercontrols / forms? The scope of the setting should ideally be a solution or a project.
In other words I want to say that this project/solution should be localizable, and then if I add a new form or control VS should automatically set the property to true.
Edit:
Although custom templates are possible, in a larger team they might not be always used. So it's more about enforcing a policy, ensuring that the team members do not ommit to set the property for the projects/solutions where it is a requirement that all forms/controls containing text resources should be localizable.
Note: Team Foundation Server is not an Option.
Not sure if it is worth the effort for a property that is so easy to change and so easy to see that it has the wrong value. But you can create your own item template.
For example: Project + Add User Control. Set its Localizable property to True. File + Export Template, select Item template. Next. Check the control you added. Next. Check all references, omit only the ones you'll never need. Next. Give it is good template name (say: "Localizable User Control").
You'll now have an item template available for future projects that has the property set. Repeat as necessary for other item templates, like a Form.
It's possible to write a unit test that uses reflection to determine whether a form/user control has been marked as localizable. Specifically, if a type has been marked as localizable, there will be an embedded resource file associated with the Type and that file will contain a ">>$this.Name" value. Here's some sample code:
private void CheckLocalizability()
{
try
{
Assembly activeAssembly = Assembly.GetAssembly(this.GetType());
Type[] types = activeAssembly.GetTypes();
foreach (Type type in types)
{
if (TypeIsInheritedFrom(type, "UserControl") || TypeIsInheritedFrom(type, "Form"))
{
bool localizable = false;
System.IO.Stream resourceStream = activeAssembly.GetManifestResourceStream(string.Format("{0}.resources", type.FullName));
if (resourceStream != null)
{
System.Resources.ResourceReader resourceReader = new System.Resources.ResourceReader(resourceStream);
foreach (DictionaryEntry dictionaryEntry in resourceReader)
{
if (dictionaryEntry.Key.ToString().Equals(">>$this.Name", StringComparison.InvariantCultureIgnoreCase))
{
localizable = true;
break;
}
}
}
if (!localizable)
{
Debug.Assert(false, string.Format("{0} is not marked localizable.", type.FullName));
}
}
}
}
catch (Exception ex)
{
Debug.Assert(false, string.Format("Exception occurred: Unable to check localization settings. {0}", ex.Message));
}
}
private bool TypeIsInheritedFrom(Type type, string baseType)
{
while (type != null)
{
if (type.Name.Equals(baseType, StringComparison.InvariantCultureIgnoreCase))
return true;
type = type.BaseType;
}
return false;
}
Please let me know if this helps.