Архив за месяц: Ноябрь 2014

Эффект Зайгарник


Эффект, вынесенный в заголовок, был открыт Блюмой Вульфовной Зайгарник (известным советским психологом, основоположником отечественной паатапсихологии) не в тиши кабинетов, а в немецком кафе, куда она пришла пообедать со своим руководителем.

Внимание Блюмы Вульфовны привлек официант не записывающий заказов посетителей, но выполняющих их безукоризненно. Она даже начала у него спрашивать заказы других посетителей. И в процессе выяснилось, что заказы посетителей которые не расплатились официант помнит, а вот заказ гостей которые только что расплатившись вышли - нет. Это натолкнуло ее на мысль, что лучше запоминаются именно незаконченные дела. В ходе дальнейших экспериментов это подтвердилось. Участникам экспериментов давали несколько задач и в произвольный момент времени решение прерывали. А через несколько дней просили вспомнить описание задач. Выяснилось, что условие задач решение которых прервали, запоминалось в два раза лучше, чем полностью решенных. Этот эффект запоминания незавершенных действий и получил название эффекта Зайгарник.
У этого эффекта есть как положительные, так и отрицательные стороны. С положительной стороны, он может использоваться в мнемотехниках. Но опять же, с осторожностью. Если вы не дочитали статью до конца, то вы ее и не запомните, т.к. не знаете что будет дальше. Но, как механизм воспитания, эта положительная сторона может иметь место. Например, в армии, осознанно или нет, он применяется для отрицательного подтверждения. Сержант прерывает обед, будит солдат ночью, тем самым закрепляя навык быстрой еды или засыпания. Т.к. даже через длительное время, недоеденный по причине медлительности обед будет вспоминаться лучше съеденного.
Отрицательный эффект сильнее и может приводить даже к неврологическим состояниям, но с точки зрения личной эффективности, он заключается в том, что мы не можем "выгрузить" незавершенные дела из мозга. Одно дело, второе, третье и вот вместо "думанья", мозг занимается перебором незавершенных дел. Т.е. завершая дела, мы не только получаем положительные эмоции, но и освобождаем мозг от воспоминаний обусловленных эффектом Зайгарник.
При составлении To-Do листов, чтобы избавиться от эффекта Зайгарник, необходимо планировать небольшие конкретные шаги. Поставив себе задачу в To-Do листе вида: полететь на Марс, мы кроме психического расстройства ничего не получим, но планируя один конкретный небольшой шаг, мы уменьшаем раздраженность нашего разума. Т.к. он удовлетворен, что получил задачу и знает как ее решать. Например, для полета на Марс таким шагом может стать подача заявки на Mars One. И вот уже вместо прокрастинации задачи: полететь на Марс, мы достаем камеру и записываем видео почему должны выбрать именно нас. Нет, все еще прокрастинация? Ок, делаем задачу еще проще: записать три причин, почему необходимо в миссию Mars One выбрать меня.
Кстати, в эксперименте с задачами, описанном выше, выяснились и еще две интересные закономерности. Во-первых, было выявлено три типа мотивации к решению:
1) из чувства долга:
2) из честолюбия;
3) в силу заинтересованности самим заданием.
Во-вторых, испытуемые разделились на две группы по отношению к прерыванию задания. Часть отнеслась нейтрально, а часть проявляли излишнюю эмоциональность и даже агрессивность.
Эти два эффекта к запоминанию как таковому не относятся, а больше про отношения начальник-подчиненный, так что, наверно, в другой раз об этом.

Построение графов при помощи NodeXL

В связи с производственной необходимостью, возникла потребность в компоненте для построения графов. Сходу были найдены вот эти три проекта:
http://graphx.codeplex.com/
http://graphsharp.codeplex.com/
http://nodexl.codeplex.com/
Т.к. по картинкам мне больше понравился третий, то его и пробовал. Он оказался неплох. Поэтому под катом рассказ о том, как при помощи NodeXL строить графики в WPF приложениях.

На всякий пожарный ссылка на скачивание. Качаем, распакуем, присоединяем dll-ки в проект:
Все, можно начинать использовать.
Для показа графа используется контрол NodeXLControl из пространства имен Smrf.NodeXL.Visualization.Wpf. Можно его как добавить через XAML, так и создать из кода и поместить в какой-нибудь контейнер. Дополнительных настроек не требуется.
Для данной статьи, я создал пустой WPF проект и на главную форму поместил вот такую разметку:

<Window x:Class="WpfApplication9.MainWindow"
        xmlns:node="clr-namespace:Smrf.NodeXL.Visualization.Wpf;assembly=Smrf.NodeXL.Control.Wpf"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <node:NodeXLControl x:Name="nxGraph" />
    </Grid>
</Window>

Ну а теперь, собственно построение графа. Информация о графе собрана в свойстве с говорящим именем Gpaph. Ну а вершины, соответственно, в свойстве Vertices графа. Чтобы постоянно не писать весь путь с имени контрола, можно это свойство скопировать в переменную и работать с ней:

IVertexCollection oVertices = nxGraph.Graph.Vertices;

Интерфейс у компонента достаточно понятный, например, добавление вершин осуществляется методом Add возвращающем ссылку на свежесозданную вершину:

IVertex first = oVertices.Add();
 
А вот дальше начинается проблема из-за желания разработчика сделать все максимально универсально. Настройка свойств вершины идет через метод SetValue первым параметром в который необходимо передавать строку с именем настраиваемого свойства. Сильно радует наличие предопределенного класса ReservedMetadataKeys с перечнем этих строковых значений в виде readonly полей. Т.е. настройка вершины имеет вид:

// Цвет вершины
first.SetValue(ReservedMetadataKeys.PerVertexLabelFillColor, Color.FromArgb(255, 255, 255, 11));
// Тип отображения
first.SetValue(ReservedMetadataKeys.PerVertexShape, VertexShape.Label);
// Текст вершины
first.SetValue(ReservedMetadataKeys.PerVertexLabel, "Первая"); 
Да, вы правильно поняли, что второй параметр метода SetValue типа object и во многих случаях придется  угадывать какого типа он должен быть реально по замыслу разработчиков.
Ну и еще пара вершин оформленных по другому:

IVertex second = oVertices.Add();
second.SetValue(ReservedMetadataKeys.PerColor, Color.FromArgb(255, 255, 0, 255));
// Задаем радиус вершины
second.SetValue(ReservedMetadataKeys.PerVertexRadius, 20F);
// И говорим что вершина - шарик
second.SetValue(ReservedMetadataKeys.PerVertexShape, VertexShape.Sphere);
IVertex third = oVertices.Add();
// Здесь мы тоже говорим, что сфера
third.SetValue(ReservedMetadataKeys.PerVertexShape, VertexShape.Sphere);
// Но задаем надпись
third.SetValue(ReservedMetadataKeys.PerVertexLabel, "Label");
Ок, давайте запустим наше приложение. Но перед этим вызовем метод отрисовки графа:

nxGraph.DrawGraph(); 
Вот так это выглядит:
Мы можем задавать расположение вершин принудительно.  У каждой вершины есть свойство Location, вот только проблема, это свойство из библиотеки WinForms. Нет, мы можем подключить бибилотеку System.Drawing.dll и написать что-нибудь вида:

third.Location = new System.Drawing.PointF(60, 60);

И оно даже заработает:
Но в большинстве случаев, мы можем просто при построении графа довериться авторасположению вершин:

nxGraph.DrawGraph(true);

Выглядит:
Только нужно не забывать, что при выборе такого способа все установленные вручную Location будут игнорироваться.
Хорошо, вершины разместили, переходим к ребрам. Список всех ребер лежит в свойстве Edges уже упоминавшегося свойства Graph:

IEdgeCollection oEdges = nodeXLControl.Graph.Edges;

Работа с ребрами очень похожа на работу с вершинами, только при создании ребра мы передаем две вершины которые ребро будет соединять и признак направленности ребра:

IEdge oEdge1 = oEdges.Add(first, second, true);
Ну а свойства уже по привычной схеме:

// Цвет
oEdge1.SetValue(ReservedMetadataKeys.PerColor, Color.FromArgb(255, 55, 125, 98));
// Толщина
oEdge1.SetValue(ReservedMetadataKeys.PerEdgeWidth, 3F);
// Подпись
oEdge1.SetValue(ReservedMetadataKeys.PerEdgeLabel, "Из первой во вторую");
Аналогично для остальных вершин:

// Первую и третью вершину соеденит ненаправленное ребро
IEdge oEdge2 = oEdges.Add(first, third, false);
// Цвет
oEdge1.SetValue(ReservedMetadataKeys.PerColor, Color.FromArgb(255, 55, 125, 98));
// Вторую с третьей - направленное
IEdge oEdge3 = oEdges.Add(second, third, true);
// Линия будет штрих-пунктирная
oEdge3.SetValue(ReservedMetadataKeys.PerEdgeStyle, EdgeStyle.DashDotDot);
Смотрится симпотично:
Компонент представляет возможность группировать вершины, но смотрится это не фонтан, т.к. все связи к сгруппированным вершинам отображаются. Но давайте покажу.

// Создаем группу, по умолчанию свернутую (второй параметр)
GroupInfo oGroup = new GroupInfo("GroupFirstAndSecond"true"Тут две вершины");
// Вершины объединенные группой
oGroup.Vertices.AddFirst(first);
oGroup.Vertices.AddLast(second);
// Добавляем группу в граф
nxGraph.Graph.SetValue(ReservedMetadataKeys.GroupInfo, new GroupInfo[] { oGroup });
Обратили внимание на признак свернутости? Так вот, он игнорируется... Если сейчас запустить, то мы увидим все тоже самое, что и на предыдущей картинке.
Поэтому предлагаю перейти к событиям и на них покажу работу с группами.
Событий у вершин, групп и т.д. нет. Все события собраны в контроле. Например, чтобы обрабатывать клик на первой или второй вершине со сворачиванием группы мне придется подписаться на событие:

nxGraph.VertexClick += nxGraph_VertexClick;
И в обработчике добавить проверку:

void nxGraph_VertexClick(object sender, VertexEventArgs vertexEventArgs)
{
    if (vertexEventArgs.Vertex.ID < 3) // Первая или вторая вершина
    {
        Dispatcher.BeginInvoke((Action)(() => nxGraph.CollapseGroup("GroupFirstAndSecond"true)));
    }
    if (vertexEventArgs.Vertex.ID == 3)
    {
        nxGraph.ExpandGroup("GroupFirstAndSecond"true);
    }
}
Вызов через Dispatcher вынужденная мера, т.к. после сворачивания вершины из графа скрываются и обработка клика падает.
Вот так выглядит граф после сворачивания двух вершин:
На сегодня все. Может в следующий раз покажу какой-нибудь пример приближенный к реальности на основе этого контрола.

Печать твердых копий из OOBase

ООфис  имеет встроенные средства для создания отчетов и они наверное подойдут многим. Но откровенно говоря я склоняюсь к печати документов через программно генерируемые таблицы ООКалк.

Кто еще хочет попробовать вот много кода из которого можно и для себя взять какие-то моменты. Главное собственно вызвать ООКалк с пустым документом, а по прочим вопросам документации достаточно.

Sub PrintNZP()

MsgBox "Закройте текущее сообщение и дождитесь сообщение <<Обработка документа окончена>>"

Dim oNDoc, oNSheets, oNSheet

oNDoc = StarDesktop.loadComponentFromURL( _
"private:factory/scalc", "_blank", 0, Array() )
oNSheets = oNDoc.getSheets()
oNSheet = oNSheets.getByIndex(0)


Dim sCurrentKod As String
Dim Connection As Variant
Dim Statement As Variant
Dim ResultSet As Variant
Dim UpdateStatement As Variant
Dim sKod As String
Dim dPlatmin As Double
Dim dCts As Double
Dim dPlatkop As Double
Dim iCountErr As Integer

If FormDoc_det.isModified Then
FormDoc_det().UpdateRow()
End If

GlobalScope.BasicLibraries.loadLibrary("aovc")
sCurrentKod = FormDoc_h().Columns.getByName("Kod").String
Connection = aovc.GetConnection()
Statement = Connection.CreateStatement()
ResultSet = Statement.ExecuteQuery("SELECT doc_det.*, doc_det.kol * doc_det.nv AS min, doc_det.kol * doc_det.rc / 100 AS grn, (SELECT DISTINCT name FROM cennic AS c WHERE c.kod = doc_det.koddet) AS namedet, (SELECT DISTINCT i.name FROM izdel AS i WHERE i.kod = doc_det.kodiz) AS nameiz FROM dbo.doc_det AS doc_det where doc_det.kod=" & sCurrentKod & " order by kod")

Dim iCounter as Integer
Dim I As Integer
oNSheet.GetCellByPosition(7,0).string = "А К Т"
oNSheet.GetCellByPosition(3,1).string = "инвентаризации незавершенного производства по цеху 16 на 1-е"
oNSheet.GetCellByPosition(7,0).CharHeight = 14
oNSheet.GetCellByPosition(3,1).CharHeight = 14
oNSheet.GetCellByPosition(3,1).string = FormDoc_h.Columns.GetByName("prim").String
oNSheet.GetCellByPosition(0,2).string = "№ пп"
oNSheet.GetCellRangeByPosition(0,2,0,3).Merge(True)
oNSheet.GetCellByPosition(0,2).Columns.GetByIndex(0).Width = "700"
oNSheet.GetCellByPosition(0,2).isTextWrapped = True
oNSheet.GetCellByPosition(0,2).HoriJustify = 2
oNSheet.GetCellByPosition(0,2).VertJustify = 2

oNSheet.GetCellByPosition(1,2).string = "Наименование изделий, деталей и узлов"
oNSheet.GetCellByPosition(1,2).Columns.GetByIndex(0).Width = "4200"
oNSheet.GetCellRangeByPosition(1,2,1,3).Merge(True)
oNSheet.GetCellByPosition(1,2).isTextWrapped = True
oNSheet.GetCellByPosition(1,2).HoriJustify = 2
oNSheet.GetCellByPosition(1,2).VertJustify = 2

oNSheet.GetCellByPosition(2, 2).string = "№№ деталей или узлов"
oNSheet.GetCellByPosition(2,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(2,2,2,3).Merge(True)
oNSheet.GetCellByPosition(2,2).HoriJustify = 2
oNSheet.GetCellByPosition(2,2).VertJustify = 2
oNSheet.GetCellByPosition(2,2).Columns.GetByIndex(0).Width = "1800"

oNSheet.GetCellByPosition(3, 2).string = "Ед. изм."
oNSheet.GetCellByPosition(3,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(3,2,3,3).Merge(True)
oNSheet.GetCellByPosition(3,2).HoriJustify = 2
oNSheet.GetCellByPosition(3,2).VertJustify = 2
oNSheet.GetCellByPosition(3,2).Columns.GetByIndex(0).Width = "1000"

oNSheet.GetCellByPosition(4, 2).string = "№ послед-ней закончен-ной операции"
'oNSheet.GetCellByPosition(4,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(4,2,4,3).isTextWrapped = True
oNSheet.GetCellRangeByPosition(4,2,4,3).Merge(True)
oNSheet.GetCellByPosition(4,2).HoriJustify = 2
oNSheet.GetCellByPosition(4,2).VertJustify = 2
oNSheet.GetCellByPosition(4,2).Columns.GetByIndex(0).Width = "2000"

oNSheet.GetCellByPosition(5, 2).string = "Книж-ный оста-ток"
oNSheet.GetCellByPosition(5,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(5,2,5,3).Merge(True)
oNSheet.GetCellByPosition(5,2).HoriJustify = 2
oNSheet.GetCellByPosition(5,2).VertJustify = 2
oNSheet.GetCellByPosition(5,2).Columns.GetByIndex(0).Width = "1800"


oNSheet.GetCellByPosition(6, 2).string = "Факти-ческое нали-чие"
oNSheet.GetCellByPosition(6,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(6,2,6,3).Merge(True)
oNSheet.GetCellByPosition(6,2).HoriJustify = 2
oNSheet.GetCellByPosition(6,2).VertJustify = 2
oNSheet.GetCellByPosition(6,2).Columns.GetByIndex(0).Width = "1400"

oNSheet.GetCellByPosition(7, 2).string = "Результат"
oNSheet.GetCellByPosition(7,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(7,2,8,2).Merge(True)
oNSheet.GetCellByPosition(7,2).HoriJustify = 2
oNSheet.GetCellByPosition(7,2).VertJustify = 2
'oNSheet.GetCellByPosition(7,2).Columns.GetByIndex(0).Width = "1800"


oNSheet.GetCellByPosition(7, 3).string = "Из-лиш-ки"
oNSheet.GetCellByPosition(7,3).isTextWrapped = True
oNSheet.GetCellByPosition(7,3).HoriJustify = 2
oNSheet.GetCellByPosition(7,3).VertJustify = 2
oNSheet.GetCellByPosition(7,3).Columns.GetByIndex(0).Width = "1100"

oNSheet.GetCellByPosition(8, 3).string = "Недо-стача"
oNSheet.GetCellByPosition(8,3).isTextWrapped = True
oNSheet.GetCellByPosition(8,3).HoriJustify = 2
oNSheet.GetCellByPosition(8,3).VertJustify = 2
oNSheet.GetCellByPosition(8,3).Columns.GetByIndex(0).Width = "1100"


oNSheet.GetCellByPosition(9, 2).string = "Стоимость материала"
oNSheet.GetCellByPosition(9,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(9,2,10,2).Merge(True)
oNSheet.GetCellByPosition(9,2).HoriJustify = 2
oNSheet.GetCellByPosition(9,2).VertJustify = 2
'oNSheet.GetCellByPosition(9,2).Columns.GetByIndex(0).Width = "1800"


oNSheet.GetCellByPosition(9, 3).string = "На единицу"
oNSheet.GetCellByPosition(9,3).isTextWrapped = True
oNSheet.GetCellByPosition(9,3).HoriJustify = 2
oNSheet.GetCellByPosition(9,3).VertJustify = 2
oNSheet.GetCellByPosition(9,3).Columns.GetByIndex(0).Width = "2400"

oNSheet.GetCellByPosition(10, 3).string = "На все количе-ство"
oNSheet.GetCellByPosition(10,3).isTextWrapped = True
oNSheet.GetCellByPosition(10,3).HoriJustify = 2
oNSheet.GetCellByPosition(10,3).VertJustify = 2
oNSheet.GetCellByPosition(10,3).Columns.GetByIndex(0).Width = "1900"


oNSheet.GetCellByPosition(11, 2).string = "Стоимость зарплаты"
oNSheet.GetCellByPosition(11,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(11,2,12,2).Merge(True)
oNSheet.GetCellByPosition(11,2).HoriJustify = 2
oNSheet.GetCellByPosition(11,2).VertJustify = 2

oNSheet.GetCellByPosition(11, 3).string = "На единицу"
oNSheet.GetCellByPosition(11,3).isTextWrapped = True
oNSheet.GetCellByPosition(11,3).HoriJustify = 2
oNSheet.GetCellByPosition(11,3).VertJustify = 2
oNSheet.GetCellByPosition(11,3).Columns.GetByIndex(0).Width = "2400"

oNSheet.GetCellByPosition(12, 3).string = "На все количе-ство"
oNSheet.GetCellByPosition(12,3).isTextWrapped = True
oNSheet.GetCellByPosition(12,3).HoriJustify = 2
oNSheet.GetCellByPosition(12,3).VertJustify = 2
oNSheet.GetCellByPosition(12,3).Columns.GetByIndex(0).Width = "1600"

oNSheet.GetCellByPosition(13, 2).string = "Цехо-вые рас-ходы _____%"
oNSheet.GetCellByPosition(13,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(13,2,13,3).Merge(True)
oNSheet.GetCellByPosition(13,2).HoriJustify = 2
oNSheet.GetCellByPosition(13,2).VertJustify = 2
oNSheet.GetCellByPosition(13,2).Columns.GetByIndex(0).Width = "1600"

oNSheet.GetCellByPosition(14, 2).string = "Обще-завод-ские рас-ходы _____%"
oNSheet.GetCellByPosition(14,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(14,2,14,3).Merge(True)
oNSheet.GetCellByPosition(14,2).HoriJustify = 2
oNSheet.GetCellByPosition(14,2).VertJustify = 2
oNSheet.GetCellByPosition(14,2).Columns.GetByIndex(0).Width = "1600"

oNSheet.GetCellByPosition(15, 2).string = "Всего завод-ская себе-стои-мость"
oNSheet.GetCellByPosition(15,2).isTextWrapped = True
oNSheet.GetCellRangeByPosition(15,2,15,3).Merge(True)
oNSheet.GetCellByPosition(15,2).HoriJustify = 2
oNSheet.GetCellByPosition(15,2).VertJustify = 2
oNSheet.GetCellByPosition(15,2).Columns.GetByIndex(0).Width = "1800"



Dim BasicBorder as New com.sun.star.table.BorderLine
Dim BasicBorder0 as New com.sun.star.table.BorderLine
Dim oBorder As Object
oBorder = oNSheet.getCellRangeByPosition(0,2,15,4).TableBorder
rem Werte für einen einfachen Rand
BasicBorder.Color = RGB(0, 0, 0)
BasicBorder.InnerLineWidth = 20
BasicBorder.OuterLineWidth = 20
BasicBorder.LineDistance =0

BasicBorder0.Color = RGB(0, 0, 0)
BasicBorder0.InnerLineWidth = 100
BasicBorder0.OuterLineWidth = 0
BasicBorder0.LineDistance = 0

oBorder.LeftLine = BasicBorder
oBorder.TopLine = BasicBorder
oBorder.RightLine = BasicBorder
oBorder.BottomLine = BasicBorder
oBorder.HorizontalLine = BasicBorder
oBorder.VerticalLine = BasicBorder
oNSheet.getCellRangeByPosition(0,2,15,4).TableBorder = oBorder

Dim currentKodDet As Long
Dim currentKodIz As Long
Dim Npp As Long
Dim Total As Long
Dim Count As Long

currentKodDet = -9999
currentKodIz = -9999
Npp=0
Total = 0
Count = 0

iCounter = 3

While ResultSet.Next

If ResultSet.Columns.getByName("kol").Long = 0 Then
GOTO lNext
End If

oBorder = oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder
oBorder.LeftLine = BasicBorder
oBorder.TopLine = BasicBorder
oBorder.RightLine = BasicBorder
oBorder.BottomLine = BasicBorder
oBorder.HorizontalLine = BasicBorder
oBorder.VerticalLine = BasicBorder
oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder = oBorder

If currentKodDet <> ResultSet.Columns.getByName("koddet").Long OR currentKodIz <> ResultSet.Columns.getByName("kodiz").Long Then

If Count > 1 Then
oNSheet.GetCellByPosition(5, iCounter).value = Total
oNSheet.GetCellByPosition(6, iCounter).value = Total
oBorder = oNSheet.getCellRangeByPosition(4, iCounter,6,iCounter).TableBorder
oBorder.TopLine = BasicBorder0
oNSheet.getCellRangeByPosition(4, iCounter,6,iCounter).TableBorder = oBorder
iCounter = iCounter + 1
End If

oBorder = oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder
oBorder.LeftLine = BasicBorder
oBorder.TopLine = BasicBorder
oBorder.RightLine = BasicBorder
oBorder.BottomLine = BasicBorder
oBorder.HorizontalLine = BasicBorder
oBorder.VerticalLine = BasicBorder
oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder = oBorder

currentKodDet = ResultSet.Columns.getByName("koddet").Long
Npp = Npp + 1
Total = 0
Count = 0
iCounter = iCounter + 1
End If

If currentKodIz <> ResultSet.Columns.getByName("kodiz").Long Then
oBorder = oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder
oBorder.LeftLine = BasicBorder
oBorder.TopLine = BasicBorder0
oBorder.RightLine = BasicBorder
oBorder.BottomLine = BasicBorder
oBorder.HorizontalLine = BasicBorder
oBorder.VerticalLine = BasicBorder
oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder = oBorder
currentKodIz = ResultSet.Columns.getByName("kodiz").Long
Npp = 1
oNSheet.GetCellByPosition(1, iCounter).string = Trim(ResultSet.Columns.getByName("nameiz").String)
oNSheet.GetCellByPosition(1, iCounter).CharHeight = 20
iCounter = iCounter + 1
EndIf

oBorder = oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder
oBorder.LeftLine = BasicBorder
oBorder.TopLine = BasicBorder
oBorder.RightLine = BasicBorder
oBorder.BottomLine = BasicBorder
oBorder.HorizontalLine = BasicBorder
oBorder.VerticalLine = BasicBorder
oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder = oBorder


Count = Count + 1

If Count = 1 Then
oNSheet.GetCellByPosition(1, iCounter).string = Trim(ResultSet.Columns.getByName("namedet").String) & Rem97(ResultSet.Columns.getByName("naz").Long)
oNSheet.GetCellByPosition(0, iCounter).value = Npp
End If

oNSheet.GetCellByPosition(4, iCounter).String = sNop(ResultSet.Columns.getByName("nop").Long)
oNSheet.GetCellByPosition(5, iCounter).value = ResultSet.Columns.getByName("kol").Long
oNSheet.GetCellByPosition(6, iCounter).value = ResultSet.Columns.getByName("kol").Long

If ResultSet.Columns.getByName("nv").Double > 0 Then
oNSheet.GetCellByPosition(2, iCounter).value = ResultSet.Columns.getByName("nv").Double
oNSheet.GetCellByPosition(11, iCounter).value = ResultSet.Columns.getByName("rc").Double
Else
oNSheet.GetCellByPosition(11, iCounter).string = " - "
End If

Total = Total + ResultSet.Columns.getByName("kol").Long

iCounter = iCounter + 1
lNext: REM
Wend
If Count > 1 Then
oBorder = oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder
oBorder.LeftLine = BasicBorder
oBorder.TopLine = BasicBorder
oBorder.RightLine = BasicBorder
oBorder.BottomLine = BasicBorder
oBorder.HorizontalLine = BasicBorder
oBorder.VerticalLine = BasicBorder
oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder = oBorder
oNSheet.GetCellByPosition(5, iCounter).value = Total
oNSheet.GetCellByPosition(6, iCounter).value = Total
oBorder = oNSheet.getCellRangeByPosition(4, iCounter,6,iCounter).TableBorder
oBorder.TopLine = BasicBorder0
oNSheet.getCellRangeByPosition(4, iCounter,6,iCounter).TableBorder = oBorder
iCounter = iCounter + 1
End If
oBorder = oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder
oBorder.LeftLine = BasicBorder
oBorder.TopLine = BasicBorder
oBorder.RightLine = BasicBorder
oBorder.BottomLine = BasicBorder0
oBorder.HorizontalLine = BasicBorder
oBorder.VerticalLine = BasicBorder
oNSheet.getCellRangeByPosition(0,iCounter,15,iCounter).TableBorder = oBorder


oNSheet.GetCellByPosition(1, iCounter + 1).string = "==========================================="
MsgBox "Обработка документа окончена"

End Sub


Замечания по новой книге Н.Н. Полещука о программировании в AutoCAD 2013-2015

Замечания и пожелания по книге Н.Н. Полещука, не пропущенные цензурой Autodesk, я буду опубликовывать в этом блоге. То, что цензура пропустит, будет размещаться в соответствующей теме на форуме. Пишу замечания в Блокноте и затем копирую в блог или на форум, полагая, что выбранный мною способ обособления цитат и ссылок на их размещение в книге не вызовет каких-либо проблем в понимании. То, что я пишу - это моё личное мнение, не претендующее на абсолютную истину.

Замечания и пожелания публикуются с позиции рядового читателя, дополнительно обозначая круг интересующих меня тем, отсутствующих в указанной книге. Интересы читателя, могут отчасти не совпадать с интересами Autodesk. Возможно в будущем кто-нибудь и напишет книгу с учётом этих замечаний и пожеланий, не связывая при этом себя цепями "Autodesk Authorized Author" (хотя в это слабо верится). Я всегда выступаю за то, чтобы предоставлять информацию в как можно более точном и непредвзятом виде, без её обработки "цензурой" той или иной компании. Поскольку у меня свои собственные "политические убеждения" то, по мере необходимости, в тексте могут появляться замечания, в той или иной степени не угодные Autodesk. 

Если какая-либо излагаемая мною информация содержит ошибки, то об этом можно (и нужно) аргументированно заявлять в комментариях.

Итак, первая запись, не пропущенная цензурой в обозначенной выше теме на форуме Autodesk:

#=============================
стр. 11, Предисловие.
Абзац 2:
#=============================
> Если это для читателя критично, то он должен с особой тщательностью подойти к
выбору языка разработки. А иначе с каждой новой версией AutoCAD придётся
выпускать новую версию своего приложения.
*******************************
На мой взгляд это достаточно сомнительное утверждение. Как правило код ARX работает
во всех версиях AutoCAD, имеющих идентичное значение Major в номере сборки
(*если в них присутствует нужный программисту функционал).
Обычно это три версии (AutoCAD 2015 перескочил через одну). В то же время .Net
сборки имеют ещё большую совместимость: например, нередко можно ограничиться
двумя-тремя версиями DLL, которые будут успешно загружаться и работать в AutoCAD
2009-2015 любой разрядности (при соблюдении того же уточнения (*), что было обозначено выше).
У AutoLISP и Visual LISP совместимость с разными версиями AutoCAD ещё выше, чем
у .NET.

Что касается VBA, то до версии 7 в нём нередко наблюдались проблемы, связанные
с разрядностью AutoCAD (x86\x64), но с выходом VBA версии 7, эта проблема была в
значительной степени устранена. Касательно совместимости кода, написанного на
VBA с разными версиями AutoCAD я не силён, т.к. не пишу на этом языке. Более
точную информацию смогут дать специалисты, использующие этот язык на практике. Тем не
менее, все мы прекрасно помним, что Autodesk уже давно и весьма настоятельно
рекомендует уходить с VBA на VB.NET (или на любой др. .Net язык), предупреждая,
что планирует вовсе отказаться от использования VBA. Правда с выходом VBA 7,
Autodesk (насколько я помню), отсрочил "похороны" VBA, по
прежнему продолжая рекомендовать "уходить" с этого языка. В виду этого изучение
VBA под AutoCAD вряд ли целесообразно.

Резюмируя цитату хочу отметить, что при надлежащем отношении к процессу
разработки, компилировать код под каждую версию AutoCAD не придётся, во всяком
случае в C++, .Net языках и AutoLISP\Visual LISP. В свете обозначенной мною
информации я не вижу связи между выбором языка и потребностью компилировать код
под каждую версию AutoCAD отдельно.

#=============================
стр. 11, Предисловие.
Абзац 5:
#=============================
> Центральной можно назвать главу 2...
*******************************
Пожалуй, это всё же несколько субъективное мнение... В данном случае координаты
"центра" будут складываться из двух составляющих: предрасположенностью читателя
к тому или иному языку программирования, а так же из объёма страниц, отведённых
в книге под материал, отведённый под тот или иной язык. Например, мне в равной
степени интересен как материал, написанный на C++, так и материал, написанный на
.NET (и не только применительно к данной книге, но и вообще). Так же мне был бы
интересен материал, написанный на managed C++ (смесь C++ и .Net). Поэтому лично
я для себя не выделяю "отдельно C++" и "отдельно .Net", но смотрю на это в
комплексе.

#=============================
стр. 11, Предисловие.
Абзац 6:
#=============================
> Это можно сделать только на C++.
*******************************
Это можно сделать только на C++, если речь идёт о программировании под
AutoCAD средствами ObjectARX
. Однако используя, к примеру, MultiCAD можно в т.ч.
и средствами .Net создавать пользовательские объекты, которые будут работать в
AutoCAD, при условии подгрузки в AutoCAD соответствующих Enabler'ов (в противном
случае такие объекты будут представлены в виде proxy). Более того, один и тот же
такой управляемый DLL файл, написанный с использованием MultiCAD, может быть
успешно загружен и использован не только в AutoCAD, но и в nanoCAD, а так же в
BricsCAD. То, является ли сам по себе MultiCAD приложением ARX, выступающим в
роли обёртки - это другой вопрос. Для меня, как для программиста, интересен
набор предоставляемого им функционала, а пользователя интересует конечный
результат. Соответственно, если нужный мне пользовательский объект может быть
создан кодом .Net через обёртку MultiCAD, то наличие такой возможности так же не
стоит отрицать.
На мой взгляд это достаточно интересная информация и может дать некоторый повод
к размышлению, тем более, что книга называется "Программирование для AutoCAD
2013-2015" и из названия вовсе не следует, что контент должен ограничиваться
только тех API, которые предоставляет компания Autodesk. Я не призываю к тому,
чтобы в книгу включали информацию о MultiCAD или Teigha, но я за то, чтобы не
давать искажённой информации, вводя читателя в заблуждение, мол "вот ваша
песочница и за её границами ничего нет". Читатель имеет право получать как можно
более объективную информацию, не зависимо от того вписывается ли данный материал
в "религиозные взгляды" менеджеров Autodesk или нет. Анализ информации - это уж
дело читателя (главное, чтобы информация была действительно объективной, не однобокой).

Оценка требований по Кано

Эта статья подготовлена для блогов журнала PCWeek.
Для того, чтобы оценить, какие из требований бэклога необходимо в первую очередь взять в работу, требуется знать  значение двух параметров: сложность реализации и полезность для конечного пользователя. Ну, а дальше все просто: расставляем наши требования на следующем графике:



При первом приближении взятие в работу должно идти в следующем порядке: правая-верхняя четверть, левая верхняя четверть, правая нижняя. На левую нижнюю никогда не хватает времени. Про оценку сложности можно говорить очень много, и об этом в другой раз. Сегодня предлагаю обсудить оценку полезности, предложенную Нориаки Канов начале 80-х годов прошлого века. В рамках этой модели все требования (функции будущего продукта) разбиваются на 5 групп:
1.       Привлекательные характеристики. При наличии этих параметров пользователи будут испытывать удовлетворение. Однако, если этих функций в продукте не будет, к негативным эффектам это не приведет. Именно об этих характеристиках продукта будет рассказывать «сарафанное радио».
2.       Одномерные характеристики.  Эти характеристики вызывают удовлетворенность, если они есть, и неудовлетворенность, если их нет. К таким параметрам может относиться скорость работы приложения. При низкой скорости откликов – негатив, при высокой – удовлетворение.
3.       Обязательные характеристики. Их наличие считается обязательным, поэтому отсутствие вызывает негатив. К примеру, если ваш текстовый редактор не позволяет набирать текст, то вы его не продадите.
4.       Неважные характеристики. Пользователь к ним нейтрален. Например, для большинства пользователей того-же текстового редактора функция подсчета символов в тексте – нейтральная.
5.       Нежелательные характеристики. Это все то, что будет вызывать у пользователя раздражение. Как правило, эти характеристики возникают потому, что так было проще разрабатывать.
Для того, чтобы отнести требование к одной из этих групп, необходимо провести анкетирование пользователей. По каждому требованию задаются два вопроса:
1.       Насколько вам понравится наличие … в продукте?
2.       Как вы отнесетесь к отсутствию … в продукте?
На каждый вопрос  пользователь может выбрать один из пяти вариантов ответа:
1.       Мне это нравится
2.       Я ожидаю именно этого
3.       Мне все равно
4.       Я могу это терпеть
5.       Мне это не нравится
Для обработки полученных в опросе результатов применяется оценочная таблица следующего вида:

В нее заносится, сколько человек ответило именно так на положительный и отрицательный вопрос о требовании. Допустим, если вы анализируете требование «наличие двухфакторной авторизации», и  на положительный вопрос (наличие двухфакторной авторизации) пользователь ответил «Ожидаю», а на отрицательный (отсутствие двухфакторной авторизации) – «Могу терпеть», то его голос будет добавлен вот в эту ячейку:

В результате получим некоторые значения в зонах нашей таблицы. Если основная масса ответов о требовании пришлась на зону «Привлекательно», то, значит, эта функция может стать фишкой приложения. Когда основная масса находится в зоне «Обязательно», выбора нет, этот функционал должен быть реализован. Если лидирует зона «Нежелательно», будьте уверены, этот  функционал пользователи не оценят. Зона «Не важно» оставляет простор для выбора. И самая интересная зона - «Непонятно». Если в этой зоне много голосов, от опрашиваемые не поняли функцию, поскольку не может одновременно нравится и наличие этой функции, и ее отсутствие.
Пользуясь моделью Кано, необходимо понимать, что она не всегда применима. Например, она не может решить проблему, изложенную в теореме Эрроу. Да и Генри Форд говорил: «Если бы я слушал своих клиентов, мне бы пришлось дать им более быструю лошадь». Но если вас это не останавливает, то предложенная методика позволяет неплохо  ранжировать ценность требований в списке.
Для тех, кто захочет попробовать методику, но не готов в ручном режиме обрабатывать результаты, есть бесплатный сайт: http://www.kanosurvey.com/

Цитата дня

Есть всего два типа языков программирования: те, на которые люди всё время ругаются, и те, которые никто не использует.
 -- Bjarne Stroustrup.
Да, это тот самый Бьёрн который придумал язык C++ и написал самый известный учебник по этому языку программирования.

Препроцессор языка Си. Директивы препроцессора.

Еще в самом первом уроке, я говорил вам, что когда компилятор встречает команду #include <stdio.h> он подставляет в это место содержимое файла stdio.h. На самом деле все немножко не так. Настал час, когда вас можно посвятить в эти тонкости, раньше они были излишними.
На самом деле, компилятор никакой строчки #include <stdio.h> никогда не встретит. Я уже упоминал, что процесс преобразования кода программы в исполняемый файл не так прост. Прежде чем передать код попадет к компилятору, его обрабатывает другая программа – препроцессор.
Препроцессор - это специальная программа, которая обрабатывает исходный код, прежде чем передать его компилятору.

 Чем занимается препроцессор?

  • удаляет комментарии из кода   
  • обрабатывает директивы препроцессора
Директивы препроцессора - это все те команды, которые начинаются с символа «#».
На данный момент вы знаете как минимум две такие команды #include и #define, хотя есть и другие.
Данный список неполный. Есть и другие действия, которые выполняет препроцессор, но я о них пока рассказывать не буду.
 

Как работает препроцессор?

В работе препроцессора важно понимать две вещи:
  • Препроцессор работает строго до компилятора
  • Препроцессор просто заменяет один текст – другим
Ему вообще наплевать на синтаксис и всякие условности. Он просматривает текст, и заменяет одни его кусочки другими.
Что и на что заменяется, вы уже должны знать, но я на всякий случай напомню.
В случае директивы #include <stdio.h> сама эта строка будет заменена препроцессором на содержимое файла stdio.h.
В случае директивы #define FJ 11 все вхождения FJ заменяются на 11. Кстати, это тоже не единственный вариант использования директивы #define. Но мы пока что коснемся только этого варианта.
Стоит отметить, что если FJ встретиться внутри имени какой-нибудь переменной или в названии оператора, то она естественно ни на что не заменится. Например, если есть переменная с именем kFJ, то она так и останется kFJ а не станет после обработки препроцессором k11.
Теперь разберем пример, который был в тесте в нашей группе во Вконтакте.


 Сразу же скажу правильный ответ. Да, эта программа скомпилируется и будет работать.
Теперь разберемся, почему же она будет скомпилирована.
Как я понял, большинство смутило то, что переменная s89 не объявлена, что и должно было бы привести к ошибке компиляции. Но, теперь вооружившись знаниями о работе препроцессора, разберемся, что же, на самом деле, получает компилятор для обработки.  Итак, сначала запускается препроцессор, который получает для обработки следующий код:


#include <stdio.h>
#define G s89
int main (){
        char G =10;
        s89=12;
        // комментарий
        printf("%d  %dn", s89, G);
        return 0;
}

Препроцессор начинает  обрабатывать этот код и буквально сразу же встречает директиву #include <stdio.h>. Для него, как мы уже знаем, это команда на то, чтобы вставить вместо этой строчки содержимое файла stdio.h. Вставляем.  Получившийся код я сюда выписывать не буду, иначе это займет очень много места.
Препроцессор продолжает обработку файла и встречает команду #define G s89. Это как мы знаем, команда на то, чтобы заменить G на s89
После замены получим следующий код. (ниже приведен код только для функции main)

int main (){
        char s89 =10;
        s89=12;
       
        printf("%d  %dn", s89, s89);
        return 0;
}

Ну и походу дела, препроцессор удалит комментарий из программы, а точнее заменит его пустой строчкой.
Вот, в принципе, мы и ответили на вопрос: «Почему программа компилируется?»
Хоть многих и смутило, что в ней отсутствует переменная s89, разобравшись обстоятельно, видим, что она-то как раз есть, а вот переменной G нет. Именно такой код поступит на компиляцию. Нетрудно видеть, что он корректный.

Как посмотреть код программы, после обработки препроцессором.

Дальше материал для тех, кому хотелось бы посмотреть полный код после обработки препроцессором Си. Такая возможность предусмотрена средой Visual Studio. Для этого нужно немножко поковыряться в свойствах проекта.
Ниже пошаговая инструкция:
1 шаг.
Открываем свойства проекта. Либо используем меню «Проект -> Свойства», либо горячие клавиши «Alt+F7».



2 шаг.
Слева, в появившемся окне выбираем «Свойства конфигурации -> С/С++ -> Препроцессор». 


Затем в правой части окна, в поле «Создавать файл препроцессора»  выбираем «Без номеров строк». Нажимаем «Ок».
Теперь, после нажатия «F7» в папке с проектом появится файл с расширением *.i открыв его в любом текстовом редакторе, вы и увидите файл после обработки препроцессором.
Стоит отметить, что при этом код не компилируется, и если вы попытаетесь запустить программу «Alt+F5» среда разработки предложит вам заново построить решение и только после этого запустит программу.
 

Внимание!
Пример, описанный выше сугубо показательный. Использовать такие выкрутасы в реальном коде недопустимо. Это легко может запутать того, кто будет потом читать ваш код.



Скачать текстовую версию урока. [pdf]

Взаимодействие двух окон между собой

Т.к. на форумах MSDN буквально за последние 24 часа задают практически один и тот же вопрос дважды (тык и тык), то давайте я напишу небольшой пример. Под катом первая форма, создаст вторую форму и будет управлять ей и узнавать о событиях там происходящих. Пример простой, но надеюсь наглядный.

1. В пустой WPF проект добавляем второе окно. Даем ему имя SecondWindow. На этом окне будет компонент для ввода данных и кнопка отправить. Если то что ввели является числом, то по нажатию на кнопку "Отправить" текст будет зеленеть, если нет, то краснеть. Для этого на форму размещаем следующую разметку:

<Window x:Class="WpfApplication6.SecondWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SecondWindow" SizeToContent="WidthAndHeight">
    <StackPanel Orientation="Horizontal">
        <TextBox x:Name="tbInput" Width="150" Margin="5" />
        <Button Content="Отправить" Margin="5" Click="Button_Click" />
    </StackPanel>
</Window>

2. В коде страницы объявляем событие, через которое мы будем отправлять уведомление о нажатии кнопки и подготовленном тексте в главную форму:

public event Action<string> TextEntered;

protected void OnTextEntered(string p_text)
{
    var handler = TextEntered;
    if (handler != null)
    {
        handler(p_text);
    }
}

3. В эту же форму добавляем метод раскраски текста:
 
public void ConfirmNumber(bool p_isConfirm)
{
    if (p_isConfirm)
    {
        tbInput.Foreground = new SolidColorBrush(Colors.Green);
    }
    else
    {
        tbInput.Foreground = new SolidColorBrush(Colors.Red);
    }
}
 
4. Ну и обработчик клика на кнопке:
 
private void Button_Click(object sender, RoutedEventArgs e)
{
    OnTextEntered(tbInput.Text);
}
 
На этом со второй формой все. Как видно, у нее нет никакой логики для принятия решения о покраске текста. Только уведомление об окончании ввода и раскраска в зависимости от переданного параметра. Всю логику мы соберем на главной форме. XAML на ней править не буду, только код.
5. В код главной формы добавляем поле для хранения ссылки на вторую форму:

private SecondWindow _second;

6. Добавляем метод принимающий решение о введенном на второй форме тексте и уведомляющий ее о принятом решении:
 
private void CheckText(string p_text)
{
    int x;
    var result = false;
    if (int.TryParse(p_text, out x))
    {
        result = true;
    }
    _second.ConfirmNumber(result);
}
 
7. Ну и правим конструктор формы и пишем метод обслуживающий событие Loaded, для создания второго окна и подписывания предыдущего метода на событие второй формы.

public MainWindow()
{
    InitializeComponent();
    Loaded += MainWindow_Loaded;
}

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    _second = new SecondWindow();
    _second.TextEntered += CheckText;
    _second.Show();
}

8. Запускаем, главная форма сразу после запуска показывает вторую и вводя в ней разные значения мы можем нажимая кнопку Отправить получать разный цвет текста:
Все, главная форма может влиять на дочернюю, а дочерняя передавать по своей инициативе данные главной для обработки.

1с 8.1: Как программно открыть множественный подбор документов (справочников)?

Делаем обработку на примере подбора документа Перемещение товаров.

Создаем обработку. На панели размещаем кнопку Подбор




  
Процедура КоманднаяПанель2Подбор(Кнопка)
    ФормаВыбора = Документы.ПеремещениеТоваров.ПолучитьФормуВыбора(,ЭтаФорма);
    ФормаВыбора.МножественныйВыбор = Истина;
    Если Не ФормаВыбора.Открыта() Тогда
        ФормаВыбора.Открыть();
    КонецЕсли;   
   
КонецПроцедуры

 
 
Процедура обработки подбора
Процедура ОбработкаВыбора(ЗначениеВыбора, Источник)
МассивВыбранныхЭлементов = ЗначениеВыбора;
Для каждого СтрокаМассива Из МассивВыбранныхЭлементов Цикл
Результат = ТабличнаяЧасть1.Найти(СтрокаМассива,"ПеремещениеТоваров");
Если Результат = Неопределено Тогда
НоваяСтрока = ТабличнаяЧасть1.Добавить();
НоваяСтрока.ПеремещениеТоваров = СтрокаМассива;

КонецЕсли;

КонецЦикла; 

 
ФормаВыбора.Открыть();
КонецПроцедуры
 

 
признак множественного подбора предполагает подбор нескольких позиций в журнале,используя зажаты SHIFT или CTRL.

 
.Команда ФормаВыбора.Открыть();  
использованная в конце последней процедуры, позволяет повторно открыть форму подбора. Конечно, при таком способе журнал будет постоянно закрываться и открываться (будет заметно мигание), но это позволяет несложным способом настроить подбор.

Ветки (Branch) и слияния (Merge) в TFS


Большинство современных средств для совместного владения кодом поддерживают технологии веток и слияний. Сегодня я немного расскажу об этой штуке в TFS и покажу, как мы ее используем.
Поехали...

Механизм создания веток позволяет некоторую папку в хранилище кода скопировать в другую папку хранилища как есть. Т.е. на момент создания ветки (branch) мы получим две папки абсолютно идентичные друг-другу и отличающиеся только названием. Для создания ветки, достаточно на любой папке открыть контекстное меню  и выбрать:
Ветки всегда создаются на сервере, т.е. после выбора этого пункта и ввода имени папки в которую необходимо делать ветку, на сервере TFS будет произведено копирование всех файлов с папки источника в папку приемник. Кстати, TFS запоминает все такие ветки и даже может их визуализировать. Первая картинка к этой статье, как раз показывает ветки одного из наших проектов. Правда визуализация немного хромает, если преобразовать ветку в папку. Но об этом в другой раз.
Зачем могут быть нужны две папки с абсолютно одинаковым содержимым? На самом деле, все просто. Вы можете сколько угодно изголяться над одной из веток и потом просто ее удалить. Во второй ветке у вас останется исходный код. Согласитесь неплохой способ проводить эксперименты. Еще одна замечательная возможность предоставляемая TFS заключается в слиянии (merge) веток. Если две папки связаны между собой в результате Branche, то выбрав одну из них, мы все изменения можем слить в другую (на картинке выше пункт merge):
В открывшемся окне мы можем выбрать в какую из веток выполнить слияние и указать, перенести все различия между ветками или можем выбрать конкретные наборы изменений которые необходимо перенести. Хранилище само определяет в чем заключается разница файлов хранящихся в ветках и вносит на машине пользователя необходимые изменения в локальные файлы. Т.к. в отличии от Brunch проходящего на сервере, Merge происходит на локальной машине и его нужно возвращать на сервер в рамках набора изменений. Это связано с тем, что при внесении изменений параллельно в двух ветках, при их слиянии могут возникнуть сложности с автоматическим объединением изменений в разных ветках одного и того же файла. И эти изменения придется выверять и подтверждать вручную.
Теперь о том, как мы эти ветки используем у себя.
Во-первых, у нас есть папка с набором базовых компонентов (Core). Эта папка размножается ветками в корневые папки всех крупных проектов. Делается это для того, чтобы при необходимости внести изменения (бывает очень редко, но бывает) в базовые компоненты, можно было эти изменения обкатать на том проекте которому они нужны. Слить эти изменения в исходную ветку, убедиться что в ней все хорошо и потом из коревой ветки слить изменения с веткой Core в остальных проектах. Т.к. во всех проектах настроены автоматические билды, то если в результате слияния изменений из Core в ветки проектов что-то пойдет не так, все разработчики узнают об этом сразу. Схематически это все выглядит вот так (картинки кликабельны):
Если еще не запутал, то во-вторых, ветки у нас применяются для разделения проекта. Разработка нового функционала ведется в основной папке проекта. Ближе к концу спринта, когда основной функционал реализован, все изменения перемещаются в заранее подготовленную ветку Prerelease. В этой ветке идет стабилизация версии перед релизом. После того, как QA дает отмашку на релиз, все изменения перемещаются в ветку Release и разворачиваются в эксплуатацию. Благодаря этим трем веткам всегда есть возможность спокойно проводить стабилизацию перед релизом, не опасаясь, того, что кто-то начнет писать новый функционал и сломает тот код, который в рамках стабилизации уже был подвергнут регрессу. Также, наличие ветки с кодом идентичным развернутому в Operation позволяет править ошибки обнаруженные в процессе эксплуатации именно в том коде, который эксплуатируется. И даже если в ветке разработки поправили модель данных или сломали вообще все, есть возможность внести минимальные изменения в стабильный код и развернуть его после проверки в эксплуатацию. Выглядит это примерно так:
Ну и объединяя две предыдущие картинки, схема веток Core и проектов будет выглядеть так:
На сегодня думаю хватит, а в следующий раз я тогда покажу как ветки связаны с тегами рабочих элементов и что приходится делать для некоторых из них.