📄 article.htm
字号:
Next, methods must be added for creating the bands. This requires
specifying the text and the type of band. To keep things simple, the application
will be responsible for instantiating the desired band type. This thrusts the
implementation immediately into some complex issues of sizing and positioning,
but as always, starting simple is the best idea.<p>
The enhancements to the <code>OutlookBar</code>
class is straightforward:
<pre>public class OutlookBar : Panel
{
private int buttonHeight;
public int ButtonHeight
{
get
{
return buttonHeight;
}
set
{
buttonHeight=value;
// do recalc layout for entire bar
}
}
public OutlookBar()
{
buttonHeight=25;
}
public void AddBand(string caption, ContentPanel content)
{
BandPanel bandPanel=new BandPanel(caption, content);
Controls.Add(bandPanel);
RecalcLayout(bandPanel);
}
private void RecalcLayout(BandPanel bandPanel)
{
// the band dimensions
bandPanel.Location=new Point(0, 0);
bandPanel.Size=new Size(ClientRectangle.Width, buttonHeight);
// the contained button dimensions
bandPanel.Controls[0].Location=new Point(0, 0);
bandPanel.Controls[0].Size=new Size(ClientRectangle.Width, buttonHeight);
// the contained content panel dimensions
bandPanel.Controls[1].Location=new Point(0, buttonHeight);
bandPanel.Controls[1].Size=new Size(ClientRectangle.Width, 0);
}
}
</pre>
And results in the following display:<p>
<img src="CSOutlookBar\screenShot2.jpg" width="354" height="196"><p>
In this implementation, the button height is identified as a parameter that might be useful to externalize so that it
is under the control of the application. Obviously, when the button height is changed after the Outlook bar has been created,
the entire bar and its children will have to be recalculated.<p>
The <code>RecalcLayout</code> method for the band is a simple starting point for determining the dimensions of the button
and the associated content panel. This method has been placed in the <code>OutlookBar</code> class because other information contained
only in the <code>OutlookBar</code> will be used soon--namely, the band's index.
<p>This implementation allows for adding one band to the bar. That's a
good starting point, as details such as activating and deactivating a band can
be worked out at this point. Also notice that this implementation raises
some questions not answered in the specifications:</p>
<ul>
<li>Are all the bands added at one time during initialization?</li>
<li>Can bands be removed?</li>
<li>Can bands be added later, when the application is running?</li>
<li>Should the insertion point be specified when adding bands?</li>
</ul>
<p>This is typical of any specification. During implementation, questions
always arise that are not answered in the spec, no matter how well thought out.
In the particular case of this prototype, the answers are yes, no, no, no,
respectively. Sure makes life easier!</p>
<p><b>Multiple Bands</b></p>
<p>It will be more interesting to deal with the expansion and contraction of a
band if the prototype first allows for multiple bands. This is easily done
by keeping track of how many bands have been added, and passing the band's index
relative to the other bands to the <code>RecalcLayout</code> method. The
<code>OutlookBar.Controls.Count</code> property provides the
necessary information. Because the band's button and content control's are
child controls, these are relative to the band position, so only the band
position needs to be modified. The changes are quite simple:</p>
<pre>public void AddBand(string caption, ContentPanel content)
{
BandPanel bandPanel=new BandPanel(caption, content);
Controls.Add(bandPanel);
RecalcLayout(bandPanel, <span style="background-color: #FF00FF">Controls.Count-1</span>);
}
private void RecalcLayout(BandPanel bandPanel, int index)
{
// the band dimensions
bandPanel.Location=new Point(0, <span style="background-color: #FF00FF">buttonHeight</span><span style="background-color: #FF00FF">*index</span>);
bandPanel.Size=new Size(ClientRectangle.Width, buttonHeight);
// the contained button dimensions
bandPanel.Controls[0].Location=new Point(0, 0);
bandPanel.Controls[0].Size=new Size(ClientRectangle.Width, buttonHeight);
// the contained content panel dimensions
bandPanel.Controls[1].Location=new Point(0, buttonHeight);
bandPanel.Controls[1].Size=new Size(ClientRectangle.Width, 0);
}
</pre>
resulting in:<p>
<img src="CSOutlookBar\screenShot3.jpg" width="266" height="204"><p>
<b>Activating A Band</b>
<p>
Activating a band requires keeping track of the band
index that is active and recalculating the layout of the entire bar when a band
is activated. Firstly, this requires handling the button click event.
A single event sink will be used for all the buttons, so the button needs to
have some information about itself so that it can notify the <code>OutlookBar</code> that it's associated band should be selected.
This information is preserved in the instance of the button itself. (Events are a wonderful thing in the .NET framework, and one feature is that
events can be associated with an instance of an object). A variety of information might want to be associated with the band's
button, but for now the index and <code>OutlookBar</code> instance will suffice. A simple class can handle this:<p>
<pre>internal class BandTagInfo
{
public OutlookBar outlookBar;
public int index;
public BandTagInfo(OutlookBar ob, int index)
{
outlookBar=ob;
this.index=index;
}
}
</pre>
And the following modification is made to the <code>AddBand</code> method:<p>
<pre>public void AddBand(string caption, ContentPanel content)
{
<span style="background-color: #FF00FF">int</span><span style="background-color: #FF00FF"> index=</span><span style="background-color: #FF00FF">Controls.Count</span><span style="background-color: #FF00FF">;</span>
<span style="background-color: #FF00FF">BandTagInfo</span><span style="background-color: #FF00FF"> </span><span style="background-color: #FF00FF">bti</span><span style="background-color: #FF00FF">=new </span><span style="background-color: #FF00FF">BandTagInfo(this</span><span style="background-color: #FF00FF">, index);</span>
BandPanel bandPanel=new BandPanel(caption, content<span style="background-color: #FF00FF">, </span><span style="background-color: #FF00FF">bti</span>);
Controls.Add(bandPanel);
RecalcLayout(bandPanel<span style="background-color: #FF00FF">, index</span>);
}
</pre>
<p>
Incidentally, the rest of the classes are still simple stubs for initializing various state information:<p>
<pre>internal class BandPanel : Panel
{
public BandPanel(string caption, ContentPanel content, BandTagInfo bti)
{
BandButton bandButton=new BandButton(caption, bti);
Controls.Add(bandButton);
Controls.Add(content);
}
}
internal class BandButton : Button
{
private BandTagInfo bti;
public BandButton(string caption, BandTagInfo bti)
{
Text=caption;
FlatStyle=FlatStyle.Standard;
Visible=true;
this.bti=bti;
}
}
public abstract class ContentPanel : Panel
{
public ContentPanel()
{
// initial state
Visible=false;
}
}
internal class IconPanel : ContentPanel
{
public IconPanel()
{
}
}
internal class PanelIcon : PictureBox
{
public PanelIcon()
{
}
}
</pre>
As is evident, the <code>BandTagInfo</code> object is passed down to the <code>BandButton</code> constructor, which
is preserved by the button instance.
The event handler can now be declared and associated with the button click, which then invokes a method in the appropriate instance of the
<code>OutlookBar</code> class to perform the actual selection:<p>
<pre>internal class BandButton : Button
{
private BandTagInfo bti;
public BandButton(string caption, BandTagInfo bti)
{
Text=caption;
FlatStyle=FlatStyle.Standard;
Visible=true;
this.bti=bti;
<span style="background-color: #FF00FF">Click+=new </span><span style="background-color: #FF00FF">EventHandler(SelectBand</span><span style="background-color: #FF00FF">);</span>
}
private void SelectBand(object sender, EventArgs e)
{
bti.outlookBar.SelectBand(bti.index);
}
}
</pre>
The <code>OutlookBar</code> class is enhanced to preserve the current band selection and initialize it to "no selection". A getter and setter is provided for the property. The
setter invokes the same <code>SelectBand<code> method as clicking on the button:<p>
<pre>public class OutlookBar : Panel
{
private int buttonHeight;
private int selectedBand;
public int SelectedBand
{
get
{
return selectedBand;
}
set
{
SelectBand(value);
}
}
...
</pre>
The <code>SelectBand</code> method:<p>
<pre>public void SelectBand(int index)
{
selectedBand=index;
RedrawBands();
}
void RedrawBands()
{
for (int i=0; i < Controls.Count; i++)
{
BandPanel bp=Controls[i] as BandPanel;
RecalcLayout(bp, i);
}
}
</pre>
iterates through all the band controls and instructs them to recalculate their layout, so the real work is done in that method.<p>
Time to think about some things:<ul>
<li>The band, when selected, has a height that is always equal to the bar
height minus the height of all the buttons on the bar.</li>
<li>The bands before the selected band are drawn at the top of bar.</li>
<li>The bands after the selected band are drawn at the bottom of the selected
band.</li>
</ul>
<p>This makes it a little annoying to have an unselected index of "-1", as all
the bands are therefore "after" some imaginary selected band. There is a
really simple solution to this, which you can see in Outlook: one band is always
selected! Therefore, the default selection will be index "0"--the first
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -