|
I'd like to get your thoughts on this design...
So I am writing some software for personal use. I'm an independent developer, and I wanted to put together an app to track the work I'm doing and handle invoicing and billing. Conceptually I understand what I want, but programmatically I am working on the design. This is more about me learning than producing an app.
So, I am building this WPF/MVVM app in components. Most pieces are user controls. I decided that I would use relay messages to communicate between the user control a& its host.
For example, the Client View is found in the Client Center.
Each Client is viewed in its own tab in the Client Center. The Client Center has a toolbar with New, Edit, Save, Cancel and Print buttons. Since the center doesn't really know about the Client views in the tabs, the Center will broadcast a message for each function. The Client Center has an instance of a TabManager class, which is a collection of TabItems. The TabItem's Tag property contains the PK of the entity in the tab's content. So, this is available in the View Model.
As an example, when the Save button on the Client Center toolbar is clicked, then it does:
ClientViewSaveMessage message = new ClientViewSaveMessage
{
Clientid = getActiveTabEntityPK()
};
Messenger.Instance.Send<ClientViewSaveMessage>(message);
Then in the client view I have
public ClientViewModel
{
messenger.Instance.Register<ClientViewSaveMessage>(p => receiveSaveMessage(ClientViewSaveMessage)p)
}
and then the receiveSaveMessage
private void receiveSaveMessage(ClientViewSaveMessage message)
{
if(this.Client.Id == message.ClientId)
{
saveChanges();
}
}
This technique has the advantage of decoupling all the components. So far it's working well. It's easy to develop, and so far, easy to maintain.
A drawback might be that I will need a lot of messages. I could make one message that has different properties...
Any way, I'd like to hear what you guys think.
Thanks
If it's not broken, fix it until it is
|
|
|
|
|
2 things come to mind. You have low expectations of your client base (reasonable in an independant developer) as you are using a tabcontrol as your client collection UI. I would use a grid. In the late 90 after about 5 years of consulting I had well over 50 organisations I was keeping tabs on, once a client or even a quote always a potential.
One reason not to use a messaging paradigm is that it is a bitch to maintain, there is no direct link between the firer of the message and the consumer of that message. a bitch to support.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Mycroft Holmes wrote: You have low expectations of your client base (reasonable in an independant
developer) as you are using a tabcontrol as your client collection UI
I never said that. I said when a client is opened, it's opened in a new tab in the client center. There is a list of clients on the side .
Mycroft Holmes wrote: One reason not to use a messaging paradigm is that it is a bitch to maintain,
there is no direct link between the firer of the message and the consumer of
that message. a bitch to support.
That's what I said in my OP. Other than that, I cannot see any reason not to use this design,
If it's not broken, fix it until it is
|
|
|
|
|
Kevin Marois wrote: There is a list of clients on the side
Ah somewhat like the outlook bar, that makes sense.
Kevin Marois wrote: Other than that,
Yeah but support is a major factor in the cost of an app and we will go a long way to make it easier for the next poor sod who has to support the app.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
SO far it seems to be working well. Overall it's very little code, but the number of message classes is growing. I think that's where the confusion might come in. I could make one generic message class.
As far as knowing who sent a message, the call stack shows that. But I understand what you're saying.
If it's not broken, fix it until it is
|
|
|
|
|
Kevin
Can you describe in one paragraph how you constructed your dynamic tab control. I have a similar requirement (max 5 tabs). If so please email me direct (your email is disabled on the messages).
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
I'm not sure what you mean by "constructed your dynamic tab control.
If it's not broken, fix it until it is
|
|
|
|
|
I was under the impression your tabcontrol populated the tabs and content based on data rather than at design time. I have a number of ideas how to achieve this but I am trying to reduce the experimentation time required to get it working and was hoping you had already researched this and had some guidance or links to examples you had used.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Ok, I see
My tab control is bound to a class, so in the XAML I have
<TabControl Grid.Row="2"
Grid.Column="2"
x:Name="MyTabControl"
BorderBrush="#6593CF"
Background="White"
ItemsSource="{Binding TabManager.Tabs}"
SelectedItem="{Binding TabManager.ActiveTab}"
Margin="2"/>
My Tab Manager class is this
public class ViewManager : BindableBase, ITabManager
{
#region Events
public event EventHandler TabActivated;
#endregion
#region Bound Properties
private ObservableCollection<TabItem> _Tabs = new ObservableCollection<TabItem>();
public ObservableCollection<TabItem> Tabs
{
get { return _Tabs; }
set
{
if (_Tabs != value)
{
_Tabs = value;
RaisePropertyChanged("Tabs");
}
}
}
private TabItem _ActiveTab;
public TabItem ActiveTab
{
get { return _ActiveTab; }
set
{
if (_ActiveTab != value)
{
_ActiveTab = value;
RaisePropertyChanged("ActiveTab");
if (TabActivated != null)
{
TabActivated(this, new EventArgs());
}
}
}
}
private bool _TabsVisible;
public bool TabsVisible
{
get { return _TabsVisible; }
set
{
if (_TabsVisible != value)
{
_TabsVisible = value;
RaisePropertyChanged("TabsVisible");
}
}
}
#endregion
#region Public Methods
public void ActivateTab(ViewInfo TabInfo)
{
var tab = GetTab(TabInfo);
ActiveTab = tab;
}
public void AddTab(TabItem Tab, bool Activate = true, int IndexPosition = -1)
{
if (IndexPosition == -1)
{
Tabs.Add(Tab);
}
else
{
Tabs.Insert(IndexPosition, Tab);
}
if (Activate)
{
ActivateTab((ViewInfo)Tab.Tag);
TabsVisible = Tabs.Count > 0;
}
}
public bool CloseAllButThisTab(ViewInfo TabInfo)
{
bool retVal = true;
for (int i = Tabs.Count - 1; i >=0; i-- )
{
var tab = Tabs[i];
var tabInfo = (ViewInfo)tab.Tag;
if (tabInfo.View == TabInfo.View && tabInfo.EntityId == TabInfo.EntityId)
{
continue;
}
else
{
retVal = CloseTab(tabInfo);
if (!retVal)
{
break;
}
}
}
return retVal;
}
public bool CloseAllTabs()
{
bool retVal = true;
for(int i = Tabs.Count - 1; i > 0; i--)
{
var tab = Tabs[i];
retVal = CloseTab((ViewInfo)tab.Tag);
}
return retVal;
}
public bool CloseTabAtIndex(int Index)
{
Tabs.RemoveAt(Index);
return true;
}
public bool CloseActiveTab()
{
return CloseTab((ViewInfo)ActiveTab.Tag);
}
public bool CloseTab(ViewInfo TabInfo)
{
bool retVal = true;
var tab = GetTab(TabInfo);
if (tab != null)
{
ActivateTab((ViewInfo)tab.Tag);
var view = (UserControl)tab.Content;
if (view.DataContext is _CenterViewModelBase)
{
_CenterViewModelBase centerVM = (_CenterViewModelBase)view.DataContext;
for(int i = centerVM.TabManager.Tabs.Count - 1; i > -1 ; i--)
{
TabItem centerTab = centerVM.TabManager.Tabs[i];
bool closed = centerVM.TabManager.CloseTab((ViewInfo)centerTab.Tag);
if (!closed)
{
retVal = false;
break;
}
}
}
else
{
dynamic vm = (_DataEntryViewModelBase)view.DataContext;
retVal = vm.PromptSaveChanges(true);
}
if (retVal)
{
Tabs.Remove(tab);
TabsVisible = Tabs.Count > 0;
}
}
return retVal;
}
public bool HasDirtyTabs()
{
bool retVal = false;
foreach(var tab in Tabs)
{
ActivateTab((ViewInfo)tab.Tag);
var view = (UserControl)tab.Content;
_DataEntryViewModelBase vm = (_DataEntryViewModelBase)view.DataContext;
retVal = vm.IsDirty();
if (retVal)
{
break;
}
}
return retVal;
}
public bool IsTabOpen(ViewInfo TabInfo)
{
TabItem tabItem = GetTab(TabInfo);
return tabItem != null;
}
public TabItem GetTab(ViewInfo TabInfo)
{
TabItem retVal = null;
foreach (var tab in Tabs)
{
ViewInfo vi = (ViewInfo)tab.Tag;
if (vi.View == TabInfo.View && vi.EntityId == TabInfo.EntityId)
{
retVal = tab;
break;
}
}
return retVal;
}
public void RefreshTabInfo(AppMessageArgs Args)
{
foreach (var tab in Tabs)
{
UserControl view = tab.Content as UserControl;
_DataEntryViewModelBase vm = view.DataContext as _DataEntryViewModelBase;
ViewInfo vi = (ViewInfo)tab.Tag;
if (vi.View == Args.ViewType)
{
tab.Header = vm.GetEntityCaption(); ;
vi.EntityId = vm.GetEntityId();
tab.Tag = vi;
}
}
}
public bool SaveAllOpenTabs()
{
bool retVal = false;
foreach (var tab in Tabs)
{
ActivateTab((ViewInfo)tab.Tag);
var view = (UserControl)tab.Content;
_DataEntryViewModelBase vm = (_DataEntryViewModelBase)view.DataContext;
retVal = vm.PromptSaveChanges(true);
if (!retVal)
{
break;
}
}
return retVal;
}
#endregion
}
Then when I want to add a tab from a VM I do this:
private void loadView(BOMArgs Args)
{
ViewInfo tabInfo = new ViewInfo
{
View = View.BOM,
EntityId = Args.BOM.Id
};
if (tabInfo.EntityId > 0 && TabManager.IsTabOpen(tabInfo))
{
TabManager.ActivateTab(tabInfo);
}
else
{
BOMViewModel vm = new BOMViewModel(Engine);
vm.Load(Args);
UserControl view = new BOMView();
view.DataContext = vm;
TabItem tabItem = new TabItem
{
Header = vm.GetEntityCaption(),
Content = view,
Tag = tabInfo
};
TabManager.AddTab(tabItem, true);
}
}
Works perfect, although I need to de-couple the view => vm dependency in the 'else' section.
If it's not broken, fix it until it is
|
|
|
|
|
Excellent, thanks for the info, it clarified my design immensely. Have a bunch of 5s
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Ok, what am I doing wrong here???
I have a Client Entity:
public class ClientEntity : _EntityBase
{
private string _ClientName = string.Empty;
public string ClientName
{
get { return _ClientName; }
set
{
if (_ClientName != value)
{
_ClientName = value;
RaisePropertyChanged("ClientName");
IsDirty = true;
}
}
}
private PayTypeEntity _PayType;
public PayTypeEntity PayType
{
get { return _PayType; }
set
{
if (_PayType != value)
{
_PayType = value;
RaisePropertyChanged("PayType");
IsDirty = true;
}
}
}
}
In my XAML, I am binding the PayTypes combo like this:
<ComboBox Grid.Row="1"
Grid.Column="3"
ItemsSource="{Binding PayTypes}"
SelectedValue="{Binding Client.PayType.Id, Mode=TwoWay}"
SelectedValuePath="Id"
DisplayMemberPath="Caption"/>
When I select a PayType it does not get set on the ClientEntity.
If it's not broken, fix it until it is
|
|
|
|
|
You're not binding to the PayType property on the ClientEntity class; you're binding to the Id property on the PayTypeEntityClass instance returned from the PayType property on the ClientEntity class.
Try this:
<ComboBox
Grid.Row="1" Grid.Column="3"
ItemsSource="{Binding PayTypes}"
SelectedValue="{Binding Client.PayType}"
DisplayMemberPath="Caption"
/>
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
That worked. Thank you
If it's not broken, fix it until it is
|
|
|
|
|
Ok, so I'm still stuck here. I thought this was working, but it's not quite right. It saved the Id ok, but when I reopen it, it doesn't set the value back to what I selected. Here's what I have:
<ComboBox Grid.Row="1"
Grid.Column="3"
IsEnabled="{Binding Path=FieldEnableHelper.PayType}"
ItemsSource="{Binding PayTypes}"
SelectedValue="{Binding Client.PayType}"
DisplayMemberPath="Caption"
TabIndex="10"
Style="{StaticResource ComboBoxStyle}"
Width="150"/>
If it's not broken, fix it until it is
|
|
|
|
|
hi i am trying to create an navigation bar which allow me to select the page based on the dropdownlist when i select the title
e.g
home->pg1->pg2
and when pg1 is clicked a dropdownbox would dropdown and show the choices which are childs of home.
is there a name or a control which have this already?
please advice
|
|
|
|
|
Menu
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
I want to make a Choose from list in my silverlight application. like click one button it will show a list of suggestion from that you can choose.
|
|
|
|
|
Its called a combo box and it binds to a collection of objects (your class of suggestions) and a selected object, the suggestion the user has selected
Add a button on the view that populates the ObservableCollection<suggestionobject> in the Viewmodel/code behind.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Sorry but Combo box shows only one dimensional data but I have to show multiple column from that they can choose.
|
|
|
|
|
Actually a combobox can take a DataTemplate and display multiple columns.
You use a ListBox or a DataGrid, it really depends on your requirements.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Ok. but combobox is not typable. I need Sugest or Sugest Apend Combobox .
|
|
|
|
|
Tanmoy Mohish wrote: but combobox is not typable
Wat, that makes no sense, no wait do not reply I really don't want to know as I suspect I have been trolled!
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
|
hello
Google Translator will translate into this one. Please note.
I have to use SVL and Naudio.
I need to develop an equalizer.
Included SVL, but it does not seem to be making is completed.
In spite of the language barrier, and ask
Please help me....
[Fields]
private static NAudioEngine instance;
private static NAudioEngine instance2;
private static NAudioEngine instance3;
private readonly DispatcherTimer positionTimer = new DispatcherTimer(DispatcherPriority.ApplicationIdle);
private readonly BackgroundWorker waveformGenerateWorker = new BackgroundWorker();
private readonly int fftDataSize = (int)FFTDataSize.FFT2048;
private bool disposed;
private bool canPlay;
private bool canPause;
private bool canStop;
private bool isPlaying;
private bool inChannelTimerUpdate;
private double channelLength;
private double channelPosition;
private bool inChannelSet;
private IWavePlayer waveOutDevice = new DirectSoundOut(50);
private WaveStream activeStream;
private WaveChannel32 inputStream;
private SampleAggregator sampleAggregator;
private SampleAggregator waveformAggregator;
private string pendingWaveformPath;
private float[] fullLevelData;
private float[] waveformData;
private TagLib.File fileTag;
private TimeSpan repeatStart;
private TimeSpan repeatStop;
private bool inRepeatSet;
private double volumeChange;
private double volumeChange_Master;
private bool allPlay;
private bool allStop;
private bool allPause;
private double equalizerChange;
private EqualizerEffect m_eqEffect;
[Apply Equalizer]
if (waveOutDevice != null)
{
waveOutDevice.Pause();
switch (i)
{
case 1:
m_eqEffect.LoGainFactor.Value = (float)d;
m_eqEffect.LoDriveFactor.Value = (float)d;
break;
case 2:
m_eqEffect.MedGainFactor.Value = (float)d;
m_eqEffect.MedDriveFactor.Value = (float)d;
break;
case 3:
m_eqEffect.HiGainFactor.Value = (float)d;
m_eqEffect.HiDriveFactor.Value = (float)d;
break;
}
m_eqEffect.OnFactorChanges();
waveOutDevice.Play();
}
[File Open]
if (System.IO.File.Exists(path))
{
try
{
waveOutDevice= new DirectSoundOut(50);
if (path.EndsWith(".wav"))
{
ActiveStream = new WaveFileReader(path);
}
else if (path.EndsWith(".mp3"))
{
ActiveStream = new Mp3FileReader(path);
}
else
{
throw new InvalidOperationException("Unsupported extension");
}
if (ActiveStream.WaveFormat.Encoding != WaveFormatEncoding.Pcm)
{
ActiveStream = WaveFormatConversionStream.CreatePcmStream(ActiveStream);
ActiveStream = new BlockAlignReductionStream(ActiveStream);
}
if (ActiveStream.WaveFormat.BitsPerSample != 16)
{
var format = new WaveFormat(ActiveStream.WaveFormat.SampleRate,
16, ActiveStream.WaveFormat.Channels);
ActiveStream = new WaveFormatConversionStream(format, ActiveStream);
}
inputStream = new WaveChannel32(ActiveStream);
sampleAggregator = new SampleAggregator(fftDataSize);
inputStream.Sample += inputStream_Sample;
ChannelLength = inputStream.TotalTime.TotalSeconds;
FileTag = TagLib.File.Create(path);
GenerateWaveformData(path);
CanPlay = true;
inputStream.Volume = 0.5f;
waveOutDevice.Volume = 1f;
m_eqEffect = new EqualizerEffect();
m_eqEffect.SampleRate = activeStream.WaveFormat.SampleRate;
m_eqEffect.LoDriveFactor.Value = 75;
m_eqEffect.LoGainFactor.Value = 0;
m_eqEffect.MedDriveFactor.Value = 40;
m_eqEffect.MedGainFactor.Value = 0;
m_eqEffect.HiDriveFactor.Value = 30;
m_eqEffect.HiGainFactor.Value = 0;
m_eqEffect.Init();
m_eqEffect.OnFactorChanges();
ActiveStream = new DSPEffectStream(inputStream, m_eqEffect);
waveOutDevice.Init(inputStream);
}
catch
{
ActiveStream = null;
CanPlay = false;
}
}
Used 3 instance
Used IWavePlayer
Help me.....
Trouble apply anyhow ...
|
|
|
|
|
Hello
I am using DrawingContext to draw text, and then i print visual throu PrintDialog. I want to convert input from milimeters. I do not know formula and on google i only found a few pieces, but i realy want to be printer independet.
I found on a Some page
1 mm = 3.779528 px
Is this printer independet. I am writing an application that will print on normal A4 printer or on 60mm x Reciept type printers. My app is already printing, but some values i want to be in pixel and some in mm.
Thank you
Edit: Upon remote testing i noticed that no matter if Printer is 600DPI or 300dpi. I can simply ignore these values
modified 22-Jul-13 5:30am.
|
|
|
|