The elegant Action Syntax in the MVCContrib Grid gives us the Empty() method. However, the default behavior of MvcContrib.UI.Grid.GridRenderer<T>.RenderHeader() is to hide the table column headers when the grid is empty. Is there a way to show headers when data is not present that does not require a major refactoring?
Now I have heard of hiding the headers by default and hard-coding something but this is not cool to me.
By the way, this is what is happening under the hood (in MvcContrib.UI.Grid.GridRenderer<T>):
protected virtual bool RenderHeader()
{
//No items - do not render a header.
if(! ShouldRenderHeader()) return false;
RenderHeadStart();
foreach(var column in VisibleColumns())
{
//Allow for custom header overrides.
if(column.CustomHeaderRenderer != null)
{
column.CustomHeaderRenderer(new RenderingContext(Writer, Context, _engines));
}
else
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
}
RenderHeadEnd();
return true;
}
protected virtual bool ShouldRenderHeader()
{
return !IsDataSourceEmpty();
}
protected bool IsDataSourceEmpty()
{
return DataSource == null || !DataSource.Any();
}
You can override the ShouldRenderHeader() method of the HtmlTableGridRenderer class.
public class AlwaysRenderHeaderRenderer<T>
: HtmlTableGridRenderer<T> where T : class
{
protected override bool ShouldRenderHeader()
{
return true;
}
}
<%= Html.Grid(Model).RenderUsing(new AlwaysRenderHeaderRenderer<TypeOfModel>()) %>
A side effect of this approach is that when the grid is empty, an empty table body will be rendered instead of a message. Any text provided to Empty() is ignored. This wasn't a problem for me since I'm manipulating the table on the client side with JavaScript anyway, but be warned.
When inheriting from HtmlTableGridRenderer you can also override RenderEmpty to eliminate the problem Brant ran into.
protected override void RenderEmpty()
{
RenderHeadStart();
foreach(var column in VisibleColumns())
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
RenderHeadEnd();
RenderBodyStart();
RenderText("<tr><td colspan=\"" + VisibleColumns().Count() + "\">" + GridModel.EmptyText + "</td></tr>");
RenderBodyEnd();
}
In your custom Grid Renderer (subclass GridRenderer<T>) use overrides like these:
/// <summary>
/// Renders the <c>table</c> header.
/// </summary>
/// <returns>
/// Returns the negative of the results
/// of <see cref="GridRenderer<T>.IsDataSourceEmpty"/>.
/// </returns>
/// <remarks>
/// The return value of <see cref="GridRenderer<T>.IsDataSourceEmpty"/>
/// in this override has no effect on whether the Grid header is rendered.
///
/// However, this return value is used
/// by <see cref="GridRenderer<T>.Render"/>
/// to run <see cref="GridRenderer<T>.RenderItems"/>
/// or <see cref="GridRenderer<T>.RenderEmpty"/>.
/// </remarks>
protected override bool RenderHeader()
{
RenderHeadStart();
foreach(var column in VisibleColumns())
{
//Allow for custom header overrides.
if(column.CustomHeaderRenderer != null)
{
column.CustomHeaderRenderer(new RenderingContext(Writer, Context, _engines));
}
else
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
}
RenderHeadEnd();
return !base.IsDataSourceEmpty();
}
…
protected override void RenderEmpty()
{
RenderBodyStart();
WriteNoRecordsAvailable(base.Writer, this._numberOfTableColumns);
RenderBodyEnd();
}
Note that WriteNoRecordsAvailable() is my custom method so it can be ignored.
Finally:
/// <summary>
/// This private member is duplicated
/// in order to override <see cref="GridRenderer<T>.RenderHeader"/>.
/// </summary>
readonly ViewEngineCollection _engines;
…
/// <summary>
/// Initializes a new instance of the <see cref="CrmHtmlTableGridRenderer<T>"/> class.
/// </summary>
/// <param name="engines">The engines.</param>
public CrmHtmlTableGridRenderer(ViewEngineCollection engines)
: base(engines)
{
_engines = engines;
}
Can't you just comment out the check to see if it should render the header. Am I missing something or do you want it to always display the header?
If so then comment out that line.
//if(! ShouldRenderHeader()) return false;
I haven't looks at all the code but from your code snippet it seems like that should work.
A combination of David's and Brant's answers:
protected override bool ShouldRenderHeader()
{
return true;
}
// Oddly Render relies on ShouldRenderHeader to return IsDataSourceEmpty
// so RenderItems will always be called.
protected override void RenderItems()
{
if (IsDataSourceEmpty())
RenderEmpty();
else
base.RenderItems();
}
protected override void RenderEmpty()
{
RenderBodyStart();
RenderText("<tr><td colspan=\"" + VisibleColumns().Count() + "\">" + GridModel.EmptyText + "</td></tr>");
RenderBodyEnd();
}
Related
In a grid I want to display different elements. Depending on the type of the element I want to use a different TemplateRenderer. One solution to this problem would be to use dom-if elements in the template. if the «if» parameter is false, the element should not get rendered. The problem is, that in my templates all the elements get rendered, even though the debugger shows me that the method, that is responsible for determining the truth value, sometimes returns false.
every template gets rendered twice
Here is my code for the grid:
// these are the two javascript templates
#JsModule("./src/views/parts/card/graphics-card-card.js")
#JsModule("./src/views/parts/card/memory-card.js")
public class PcPartsDomIfGrid extends Grid<AbstractPcPart> {
private static final long serialVersionUID = 7474489703766322949L;
public PcPartsDomIfGrid() {
super();
initColumn();
}
private boolean isMemory(AbstractPcPart pcPart) {
return pcPart.getClass().equals(Memory.class);
}
private boolean isGraphicsCard(AbstractPcPart pcPart) {
return pcPart.getClass().equals(GraphicsCard.class);
}
private void initColumn() {
addColumn(Objects.requireNonNull(CardFactory.getTemplate())
.withProperty("partCard", CardFactory::create)
.withProperty("isMemory", this::isMemory)
.withProperty("isGraphicsCard", this::isGraphicsCard));
}
}
Here is the Code for the Factory:
public class CardFactory {
public static AbstractPcPartCard create(AbstractPcPart pcPart) {
if (pcPart.getClass().equals(GraphicsCard.class)) {
return GraphicsCardCard.create((GraphicsCard) pcPart);
} else if (pcPart.getClass().equals(Memory.class)) {
return MemoryCard.create((Memory) pcPart);
} else {
// different PC parts
return null;
}
}
public static TemplateRenderer<AbstractPcPart> getTemplate() {
String memoryTemplate = MemoryCard.getTemplateString();
String graphicsCardTemplate = GraphicsCardCard.getTemplateString();
String combinedTemplate = memoryTemplate + graphicsCardTemplate;
return TemplateRenderer.of(combinedTemplate);
}
}
Both MemoryCard and GraphicsCardCard are similar, here is the code for MemoryCard:
public class MemoryCard extends AbstractPcPartCard {
Memory pcPart;
public MemoryCard(Memory pcPart) {
this.pcPart = pcPart;
}
public static MemoryCard create(Memory pcPart) {
return new MemoryCard(pcPart);
}
// getters
public static String getTemplateString() {
return "<memory-card is='dom-if' if='[[item.isMemory]]'"
+ " part-card='[[item.partCard]]'>"
+ "</memory-card>";
}
}
The complete code can be found on github. The relevant view is located in the package:
com.example.application.ui.views.partsDomIf
The dom-if attribute must be used on a <template> tag, such as
<template is='dom-if' if='[[item.important]]'>this is shown when the item is <b>important</b></template>
<template is='dom-if' if='[[!item.important]]'>this is shown when the item is <b>NOT important</b></template>
Note that the !property.name negation is the only logical operation available, you can't use e.g. comparators there.
The pattern of using a factory method CardFactory::create also does not make much sense to me. The Grid receives a list of items through the DataProvider (either through setItems or setDataProvider) and the Renderer just processes those items to show them in the UI. You should not create new data objects in the Renderer if you can avoid it.
I currently want to build a DatePicker with custom buttons on iOS using Xamarin. Out of the box there doesn't seem to be way to do.
Would anyone know how to build a custom renderer that would allow me to add a Cancel button and Next Button when the DatePickerDialog comes into focus?
I basically want this but at the top of this a Cancel button on the left and a next button on the right.
I was able to piece together a solution that does exactly what I want. Unfortunately, I had to use objectveC techniques mixed in with c# in order to get it to work. There is currently a issue triaged in the Xamarin project.
https://github.com/xamarin/Xamarin.Forms/issues/14156
For now here is my solution, which I hope helps others with their similar problems
using System;
using System.ComponentModel;
using System.Globalization;
using dcbel.Mobile.Controls;
using dcbel.Mobile.iOS.Extensions;
using dcbel.Mobile.iOS.Renderers;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CustomDatePicker), typeof(CustomDatePickerRenderer))]
namespace dcbel.Mobile.iOS.Renderers
{
/// <summary>
/// An extended date picker renderer.
/// </summary>
///
/// <seealso cref="T:Xamarin.Forms.Platform.iOS.DatePickerRenderer"/>
public class CustomDatePickerRenderer : DatePickerRenderer
{
/// <summary>
/// Executes the element property changed action.
/// </summary>
///
/// <param name="sender"> Source of the event. </param>
/// <param name="e"> A PropertyChangedEventArgs? to process. </param>
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs? e)
{
base.OnElementPropertyChanged(sender, e);
if (e != null && e.PropertyName == "DateTime" && this.Element is CustomDatePicker datePicker)
{
this.Control.Text = datePicker.DateTime.ToString(datePicker.Format, CultureInfo.CurrentCulture);
}
}
/// <summary>
/// Executes the element changed action.
/// </summary>
///
/// <param name="e"> An ElementChangedEventArgs{DatePicker} to process. </param>
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
if (this.Control != null && e?.NewElement is CustomDatePicker datePicker)
{
this.Control.BorderStyle = UITextBorderStyle.None;
if (this.Control.InputAccessoryView is UIToolbar toolbar)
{
var buttonTextAttributes = new UITextAttributes()
{
Font = UIFont.FromName("SFStrong", 15),
TextColor = UIColorExtensions.FromHex("5A5E62"),
};
using var doneButton = new UIBarButtonItem(datePicker.DoneButtonText, UIBarButtonItemStyle.Done, this, ObjCRuntime.Selector.FromHandle(ObjCRuntime.Selector.GetHandle("DoneButtonAction:")));
using var cancelButton = new UIBarButtonItem(datePicker.CancelButtonText, UIBarButtonItemStyle.Done, this, ObjCRuntime.Selector.FromHandle(ObjCRuntime.Selector.GetHandle("CancelButtonAction:")));
using var title = new UIBarButtonItem(datePicker.TitleText, UIBarButtonItemStyle.Plain, null) { Enabled = false };
using var space = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
doneButton.SetTitleTextAttributes(buttonTextAttributes, UIControlState.Normal);
title.SetTitleTextAttributes(buttonTextAttributes, UIControlState.Normal);
title.SetTitleTextAttributes(buttonTextAttributes, UIControlState.Disabled);
cancelButton.SetTitleTextAttributes(buttonTextAttributes, UIControlState.Normal);
toolbar.SetItems(new UIBarButtonItem[] { cancelButton, space, title, space, doneButton }, true);
}
if (this.Control.InputView is UIDatePicker uiDatePicker)
{
uiDatePicker.Mode = UIDatePickerMode.DateAndTime;
}
}
}
[Action("CancelButtonAction:")]
private void CancelButtonAction(NSObject sender)
{
this.Control.ResignFirstResponder();
this.Element.Unfocus();
}
[Action("DoneButtonAction:")]
private void DoneButtonAction(NSObject sender)
{
var barButton = (UIBarButtonItem)sender;
if (barButton.Target is CustomDatePickerRenderer datePickerRenderer)
{
if (datePickerRenderer.Control?.InputView is UIDatePicker uiDatePicker)
{
var dateSelected = uiDatePicker.Date.ToDateTime();
((CustomDatePicker)this.Element).DateTime = dateSelected;
}
}
this.Control.ResignFirstResponder();
this.Element.Unfocus();
}
}
}
How to use DecorateAllWith to decorate with a DynamicProxy all instances implements an interface?
For example:
public class ApplicationServiceInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
// ...
invocation.Proceed();
// ...
}
}
public class ApplicationServiceConvention : IRegistrationConvention
{
public void Process(Type type, Registry registry)
{
if (type.CanBeCastTo<IApplicationService>() && type.IsInterface)
{
var proxyGenerator = new ProxyGenerator();
// ??? how to use proxyGenerator??
// ???
registry.For(type).DecorateAllWith(???); // How to use DecorateAllWith DynamicProxy ...??
}
}
}
I could decorate some interfaces to concrete types using (for example):
var proxyGenerator = new ProxyGenerator();
registry.For<IApplicationService>().Use<BaseAppService>().DecorateWith(service => proxyGenerator.CreateInterfaceProxyWithTargetInterface(....))
But havent able to using DecorateAll to do this.
To call registry.For<>().Use<>().DecorateWith() I have to do this:
if (type.CanBeCastTo<IApplicationService>() && !type.IsAbstract)
{
var interfaceToProxy = type.GetInterface("I" + type.Name);
if (interfaceToProxy == null)
return null;
var proxyGenerator = new ProxyGenerator();
// Build expression to use registration by reflection
var expression = BuildExpressionTreeToCreateProxy(proxyGenerator, type, interfaceType, new MyInterceptor());
// Register using reflection
var f = CallGenericMethod(registry, "For", interfaceToProxy);
var u = CallGenericMethod(f, "Use", type);
CallMethod(u, "DecorateWith", expression);
}
Only for crazy minds ...
I start to get very tired of StructureMap, many changes and no documentation, I have been read the source code but ... too many efforts for my objective ...
If someone can give me a bit of light I will be grateful.
Thanks in advance.
In addition ... I post here the real code of my helper to generate the expression tree an register the plugin family:
public static class RegistrationHelper
{
public static void RegisterWithInterceptors(this Registry registry, Type interfaceToProxy, Type concreteType,
IInterceptor[] interceptors, ILifecycle lifecycle = null)
{
var proxyGenerator = new ProxyGenerator();
// Generate expression tree to call DecoreWith of StructureMap SmartInstance type
// registry.For<interfaceToProxy>().Use<concreteType>()
// .DecoreWith(ex => (IApplicationService)
// proxyGenerator.CreateInterfaceProxyWithTargetInterface(interfaceToProxy, ex, interceptors)
var expressionParameter = Expression.Parameter(interfaceToProxy, "ex");
var proxyGeneratorConstant = Expression.Constant(proxyGenerator);
var interfaceConstant = Expression.Constant(interfaceToProxy);
var interceptorConstant = Expression.Constant(interceptors);
var methodCallExpression = Expression.Call(proxyGeneratorConstant,
typeof (ProxyGenerator).GetMethods().First(
met => met.Name == "CreateInterfaceProxyWithTargetInterface"
&& !met.IsGenericMethod && met.GetParameters().Count() == 3),
interfaceConstant,
expressionParameter,
interceptorConstant);
var convert = Expression.Convert(methodCallExpression, interfaceToProxy);
var func = typeof(Func<,>).MakeGenericType(interfaceToProxy, interfaceToProxy);
var expr = Expression.Lambda(func, convert, expressionParameter);
// Register using reflection
registry.CallGenericMethod("For", interfaceToProxy, new[] {(object) lifecycle /*Lifecicle*/})
.CallGenericMethod("Use", concreteType)
.CallNoGenericMethod("DecorateWith", expr);
}
}
public static class CallMethodExtensions
{
/// <summary>
/// Call a method with Generic parameter by reflection (obj.methodName[genericType](parameters)
/// </summary>
/// <returns></returns>
public static object CallGenericMethod(this object obj, string methodName, Type genericType, params object[] parameters)
{
var metod = obj.GetType().GetMethods().First(m => m.Name == methodName && m.IsGenericMethod);
var genericMethod = metod.MakeGenericMethod(genericType);
return genericMethod.Invoke(obj, parameters);
}
/// <summary>
/// Call a method without Generic parameter by reflection (obj.methodName(parameters)
/// </summary>
/// <returns></returns>
public static object CallNoGenericMethod(this object obj, string methodName, params object[] parameters)
{
var method = obj.GetType().GetMethods().First(m => m.Name == methodName && !m.IsGenericMethod);
return method.Invoke(obj, parameters);
}
}
Almost two years later I have needed return this issue for a new project. This time I have solved it this time I have used StructureMap 4.
You can use a custom interceptor policy to decorate an instance in function of his type. You have to implement one interceptor, one interceptor policy and configure it on a registry.
The Interceptor
public class MyExInterceptor : Castle.DynamicProxy.IInterceptor
{
public void Intercept(Castle.DynamicProxy.IInvocation invocation)
{
Console.WriteLine("-- Call to " + invocation.Method);
invocation.Proceed();
}
}
The interceptor policy
public class CustomInterception : IInterceptorPolicy
{
public string Description
{
get { return "good interception policy"; }
}
public IEnumerable<IInterceptor> DetermineInterceptors(Type pluginType, Instance instance)
{
if (pluginType == typeof(IAppService))
{
// DecoratorInterceptor is the simple case of wrapping one type with another
// concrete type that takes the first as a dependency
yield return new FuncInterceptor<IAppService>(i =>
(IAppService)
DynamicProxyHelper.CreateInterfaceProxyWithTargetInterface(typeof(IAppService), i));
}
}
}
Configuration
var container = new Container(_ =>
{
_.Policies.Interceptors(new CustomInterception());
_.For<IAppService>().Use<AppServiceImplementation>();
});
var service = container.GetInstance<IAppService>();
service.DoWork();
You can get a working example on this gist https://gist.github.com/tolemac/3e31b44b7fc7d0b49c6547018f332d68, in the gist you can find three types of decoration, the third is like this answer.
Using it you can configure the decorators of your services easily.
I have an external dll written in C# and I studied from the assemblies documentation that it writes its debug messages to the Console using Console.WriteLine.
this DLL writes to console during my interaction with the UI of the Application, so i don't make DLL calls directly, but i would capture all console output , so i think i got to intialize in form load , then get that captured text later.
I would like to redirect all the output to a string variable.
I tried Console.SetOut, but its use to redirect to string is not easy.
As it seems like you want to catch the Console output in realtime, I figured out that you might create your own TextWriter implementation that fires an event whenever a Write or WriteLine happens on the Console.
The writer looks like this:
public class ConsoleWriterEventArgs : EventArgs
{
public string Value { get; private set; }
public ConsoleWriterEventArgs(string value)
{
Value = value;
}
}
public class ConsoleWriter : TextWriter
{
public override Encoding Encoding { get { return Encoding.UTF8; } }
public override void Write(string value)
{
if (WriteEvent != null) WriteEvent(this, new ConsoleWriterEventArgs(value));
base.Write(value);
}
public override void WriteLine(string value)
{
if (WriteLineEvent != null) WriteLineEvent(this, new ConsoleWriterEventArgs(value));
base.WriteLine(value);
}
public event EventHandler<ConsoleWriterEventArgs> WriteEvent;
public event EventHandler<ConsoleWriterEventArgs> WriteLineEvent;
}
If it's a WinForm app, you can setup the writer and consume its events in the Program.cs like this:
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
using (var consoleWriter = new ConsoleWriter())
{
consoleWriter.WriteEvent += consoleWriter_WriteEvent;
consoleWriter.WriteLineEvent += consoleWriter_WriteLineEvent;
Console.SetOut(consoleWriter);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
static void consoleWriter_WriteLineEvent(object sender, Program.ConsoleWriterEventArgs e)
{
MessageBox.Show(e.Value, "WriteLine");
}
static void consoleWriter_WriteEvent(object sender, Program.ConsoleWriterEventArgs e)
{
MessageBox.Show(e.Value, "Write");
}
It basically amounts to the following:
var originalConsoleOut = Console.Out; // preserve the original stream
using(var writer = new StringWriter())
{
Console.SetOut(writer);
Console.WriteLine("some stuff"); // or make your DLL calls :)
writer.Flush(); // when you're done, make sure everything is written out
var myString = writer.GetStringBuilder().ToString();
}
Console.SetOut(originalConsoleOut); // restore Console.Out
So in your case you'd set this up before making calls to your third-party DLL.
You can also call SetOut with Console.OpenStandardOutput, this will restore the original output stream:
Console.SetOut(new StreamWriter(Console.OpenStandardOutput()));
Or you can wrap it up in a helper method that takes some code as an argument run it and returns the string that was printed. Notice how we gracefully handle exceptions.
public string RunCodeReturnConsoleOut(Action code)
{
string result;
var originalConsoleOut = Console.Out;
try
{
using (var writer = new StringWriter())
{
Console.SetOut(writer);
code();
writer.Flush();
result = writer.GetStringBuilder().ToString();
}
return result;
}
finally
{
Console.SetOut(originalConsoleOut);
}
}
Using solutions proposed by #Adam Lear and #Carlo V. Dango I created a helper class:
public sealed class RedirectConsole : IDisposable
{
private readonly Action<string> logFunction;
private readonly TextWriter oldOut = Console.Out;
private readonly StringWriter sw = new StringWriter();
public RedirectConsole(Action<string> logFunction)
{
this.logFunction = logFunction;
Console.SetOut(sw);
}
public void Dispose()
{
Console.SetOut(oldOut);
sw.Flush();
logFunction(sw.ToString());
sw.Dispose();
}
}
which can be used in the following way:
public static void MyWrite(string str)
{
// print console output to Log/Socket/File
}
public static void Main()
{
using(var r = new RedirectConsole(MyWrite)) {
Console.WriteLine("Message 1");
Console.WriteLine("Message 2");
}
// After the using section is finished,
// MyWrite will be called once with a string containing all messages,
// which has been written during the using section,
// separated by new line characters
}
I downloaded the code from code.google and get the last version v0.5.2
I set a field in bcd fix format,which is N-6 in bcd format(bit._003_proc_code)
For ex:
*Field definition:
DefaultTemplate =new Template
{
{ Bit._002_PAN, FieldDescriptor.BcdVar(2, 19,Formatters.Ascii) },
{ Bit._003_PROC_CODE, FieldDescriptor.BcdFixed(3)},
{ Bit._004_TRAN_AMOUNT, FieldDescriptor.BcdFixed(6) },
..............
}
usage:
Iso8583 msg =new Iso8584();
msg[3]="000000";
when i unpack the message ,i can only get “0000” from message 3 .
is this a bug or error in definition
I would wait to hear from John Oxley to see if this is just a coding error. Now that I said that, I think it is a bug.
I had problems with the BcdFixed definition and ended up creating a new Fixed Length BCD formatter to work around the problem.
Here is what I did:
I created a class called FixedLengthBcdFormatter which is a variation of the FixedLengthFormatter class.
///
/// Fixed field formatter
///
public class FixedLengthBcdFormatter : ILengthFormatter
{
private readonly int _packedLength;
private readonly int _unPackedLength;
///<summary>
/// Fixed length field formatter
///</summary>
///<param name = "unPackedLength">The unpacked length of the field.</param>
public FixedLengthBcdFormatter(int unPackedLength)
{
_unPackedLength = unPackedLength;
double len = _unPackedLength;
_packedLength = (int)Math.Ceiling(len / 2);
}
#region ILengthFormatter Members
/// <summary>
/// Get the length of the packed length indicator. For fixed length fields this is 0
/// </summary>
public int LengthOfLengthIndicator
{
get { return 0; }
}
/// <summary>
/// The maximum length of the field displayed as a string for descriptors
/// </summary>
public string MaxLength
{
get { return _unPackedLength.ToString(); }
}
/// <summary>
/// Descriptor for the length formatter used in ToString methods
/// </summary>
public string Description
{
get { return "FixedBcd"; }
}
/// <summary>
/// Get the length of the field
/// </summary>
/// <param name = "msg">Byte array of message data</param>
/// <param name = "offset">offset to start parsing</param>
/// <returns>The length of the field</returns>
public int GetLengthOfField(byte[] msg, int offset)
{
return _unPackedLength;
}
/// <summary>
/// Pack the length header into the message
/// </summary>
/// <param name = "msg">Byte array of the message</param>
/// <param name = "length">The length to pack into the message</param>
/// <param name = "offset">Offset to start the packing</param>
/// <returns>offset for the start of the field</returns>
public int Pack(byte[] msg, int length, int offset)
{
return offset;
}
/// <summary>
/// Check the length of the field is valid
/// </summary>
/// <param name = "packedLength">the packed length of the field</param>
/// <returns>true if valid, false otherwise</returns>
public bool IsValidLength(int packedLength)
{
return packedLength == _packedLength;
}
#endregion
}
Modified the FieldDescription class / BcdFixed declaration
///
/// The bcd fixed.
///
///
/// The length.
///
///
///
public static IFieldDescriptor BcdFixed(int unpackedLength)
{
return Create(new FixedLengthBcdFormatter(unpackedLength), FieldValidators.N, Formatters.Bcd, null);
}
Then change your formatter declaration to provide the unpacked length as the paramter.
Bit._003_PROC_CODE, FieldDescriptor.BcdFixed(6)},
Again, all this may have been unnecessary because I didn't know something about the existing code, but it is working for me.
I hope this helps.